Compare commits

...
Sign in to create a new pull request.

1681 commits
fix-tos ... dev

Author SHA1 Message Date
daniel
c909e9757a
Merge pull request #3195 from pixelfed/staging
Staging
2022-01-28 23:23:43 -07:00
Daniel Supernault
5caf160633
Update compiled assets 2022-01-28 23:22:52 -07:00
Daniel Supernault
f8bc0d9c03
Update InternalApiController 2022-01-28 22:56:52 -07:00
daniel
a2c56c4e71
Merge pull request #3194 from pixelfed/staging
Staging
2022-01-28 22:20:38 -07:00
Daniel Supernault
d30a5eb311
Update compiled assets 2022-01-28 22:19:32 -07:00
Daniel Supernault
86b93efdfd
Update changelog 2022-01-28 22:18:39 -07:00
Daniel Supernault
6cfd6be523
Add ReblogService, improve reblogged state for api entities 2022-01-28 22:06:43 -07:00
Daniel Supernault
a7d715517d
Update StatusService, use BookmarkService for bookmarked state 2022-01-28 20:57:25 -07:00
Daniel Supernault
ff4519bfaf
Update i18n strings 2022-01-28 20:33:24 -07:00
Daniel Supernault
0db82ff2dd
Update PublicApiController 2022-01-28 20:29:46 -07:00
Daniel Supernault
73226360fc
Update ApiV1Controller, fix private status replies returning 404 2022-01-28 20:04:02 -07:00
Daniel Supernault
a11772bcfe
Update BookmarkService, use sorted set 2022-01-28 18:27:53 -07:00
Daniel Supernault
0157566c25
Add BookmarkService 2022-01-27 05:17:55 -07:00
Daniel Supernault
c0b1e0427e
Update PublicApiController, add bookmark state to timeline endpoints 2022-01-27 05:11:34 -07:00
Daniel Supernault
34a3d65f30
Update InternalApiController 2022-01-27 05:11:06 -07:00
Daniel Supernault
19867ce97a
Add fallback custom emoji image 2022-01-27 02:59:16 -07:00
Daniel Supernault
3c261bbfec
Update ApiV1Controller, invalidate status reply cache on new reply 2022-01-27 00:39:44 -07:00
Daniel Supernault
3d86d21be6
Update BaseApiController, improve favourites endpoint 2022-01-26 23:48:32 -07:00
Daniel Supernault
f063cb0185
Update BaseApiController, improve favourites endpoint 2022-01-26 23:32:31 -07:00
Daniel Supernault
3c35158ebc
Update InternalApiController, redirect remote post and profiles to Metro 2.0 2022-01-25 02:18:50 -07:00
Daniel Supernault
16e725183e
Update ApiV1Controller, add custom_emoji endpoint 2022-01-25 02:08:50 -07:00
Daniel Supernault
d1ff86ee34
Update nav 2022-01-25 01:58:20 -07:00
daniel
80ac325fb6
Merge pull request #3186 from pixelfed/staging
Update atom feeds, include media alt text. Fixes #3184
2022-01-25 00:50:39 -07:00
Daniel Supernault
fea2faaba8
Update compiled assets 2022-01-25 00:50:10 -07:00
Daniel Supernault
c6d2db68a0
Update changelog 2022-01-25 00:40:59 -07:00
Daniel Supernault
5d9b6863b6
Update atom feeds, include media alt text. Fixes #3184 2022-01-25 00:39:46 -07:00
Daniel Supernault
8c54ab57ac
Update web routes, fix atom feeds for account usernames containing a dot 2022-01-25 00:05:00 -07:00
daniel
daeccef756
Merge pull request #3185 from pixelfed/staging
Staging
2022-01-24 23:27:28 -07:00
Daniel Supernault
80c4126d48
Update changelog 2022-01-24 23:26:48 -07:00
Daniel Supernault
042c5b6c33
Improve onboarding 2022-01-24 23:26:38 -07:00
Daniel Supernault
b3d9b9e5cd
Update i18n 2022-01-24 23:14:22 -07:00
Daniel Supernault
4b6effb9c8
Update ApiV1Controller, improve follow count cache invalidation 2022-01-24 22:46:31 -07:00
daniel
86b4fd77eb
Merge pull request #3182 from pixelfed/staging
Update compiled assets
2022-01-23 03:34:53 -07:00
Daniel Supernault
0ea26415a9
Update compiled assets 2022-01-23 03:33:39 -07:00
daniel
ce29d9208b
Merge pull request #3181 from pixelfed/staging
Update ProfileController
2022-01-23 03:13:33 -07:00
Daniel Supernault
940209018a
Update ProfileController 2022-01-23 02:44:34 -07:00
daniel
ff6e2f2c12
Merge pull request #3180 from pixelfed/staging
Staging
2022-01-23 01:48:38 -07:00
Daniel Supernault
97a75e0fde
Update changelog 2022-01-23 01:48:03 -07:00
Daniel Supernault
3bd211d7f5
Update profile embeds, fix NaN bug and improve performance 2022-01-23 01:47:31 -07:00
daniel
ddd9b7ef4e
Merge pull request #3179 from pixelfed/staging
Update compiled assets
2022-01-23 00:06:18 -07:00
Daniel Supernault
803bbac0ea
Update compiled assets 2022-01-23 00:04:34 -07:00
daniel
d61c91d1a5
Merge pull request #3178 from pixelfed/staging
Staging
2022-01-22 21:19:17 -07:00
Daniel Supernault
f582041a20
Update changelog 2022-01-22 21:18:01 -07:00
Daniel Supernault
792bfaa599
Update compiled assets 2022-01-22 21:17:40 -07:00
Daniel Supernault
879281cccb
Update ComposeModal, add max file size and allowed mime types. Fixes #3162 2022-01-22 21:11:34 -07:00
Daniel Supernault
33f863e8d3
Update compiled assets 2022-01-22 20:38:16 -07:00
daniel
9f9a52b683
Merge pull request #3177 from pixelfed/staging
Staging
2022-01-22 19:44:27 -07:00
Daniel Supernault
792212b655
Cleanup 2022-01-22 19:42:37 -07:00
Daniel Supernault
127777261a
Update changelog 2022-01-22 19:38:25 -07:00
Daniel Supernault
aff7456639
Update WebfingerService. Fixes #3167 2022-01-22 19:37:50 -07:00
daniel
c7dec86ccd
Merge pull request #3176 from pixelfed/staging
Staging
2022-01-22 19:13:32 -07:00
Daniel Supernault
58f90e577b
Update compiled assets 2022-01-22 19:12:43 -07:00
Daniel Supernault
0a4505ed3a
Update PostComponent, add custom emoji support 2022-01-22 19:05:53 -07:00
Daniel Supernault
42602351c7
Update StatusTransformer, add emoji entities 2022-01-22 18:54:12 -07:00
daniel
18c49dd50c
Merge pull request #3173 from pixelfed/staging
Fix custom emoji admin dashboard bug
2022-01-21 03:20:00 -07:00
Daniel Supernault
1e00c431a8
Fix custom emoji admin dashboard bug 2022-01-21 03:19:09 -07:00
daniel
e9f3bbe8ad
Merge pull request #3172 from pixelfed/staging
Update custom emoji json-ld
2022-01-21 01:56:54 -07:00
Daniel Supernault
46d5f12590
Update custom emoji json-ld 2022-01-21 01:55:58 -07:00
daniel
b0006478ee
Merge pull request #3171 from pixelfed/staging
Update compiled assets
2022-01-21 01:50:01 -07:00
Daniel Supernault
6ec1c8468a
Update compiled assets 2022-01-21 01:32:26 -07:00
daniel
a9da420b43
Merge pull request #3166 from pixelfed/staging
Add custom emoji
2022-01-21 01:31:43 -07:00
Daniel Supernault
c4df9c4547
Update changelog 2022-01-21 01:30:09 -07:00
Daniel Supernault
dbb1638fd6
Invalidate status cache after importing custom emoji 2022-01-21 01:28:36 -07:00
Daniel Supernault
dc7962d898
Add shortcode & domain search 2022-01-21 01:12:56 -07:00
Daniel Supernault
efeaf427e1
Add CustomEmoji admin dashboard 2022-01-21 00:27:30 -07:00
Daniel Supernault
b2016e6c21
Update note transformers, add custom emoji support 2022-01-19 02:21:26 -07:00
Daniel Supernault
0d78a9aaf8
Update compiled assets 2022-01-19 01:04:25 -07:00
Daniel Supernault
489fdbb248
Update StatusTagPipeline 2022-01-19 00:58:13 -07:00
Daniel Supernault
bf2828c2ee
Add emoji directory and update .gitignore 2022-01-19 00:53:06 -07:00
Daniel Supernault
dc17c9fc27
Improve emoji import 2022-01-19 00:46:30 -07:00
Daniel Supernault
01798daf56
Update AP helpers, import Emoji tags 2022-01-18 23:26:44 -07:00
Daniel Supernault
ca79e26d3a
Add custom emoji 2022-01-18 23:03:21 -07:00
daniel
d659dfdc9a
Merge pull request #3165 from pixelfed/staging
Update compiled assets
2022-01-18 19:00:54 -07:00
Daniel Supernault
16ced7b463
Update compiled assets 2022-01-18 18:58:36 -07:00
daniel
b95e416c1e
Merge pull request #3164 from pixelfed/staging
Staging
2022-01-17 17:13:45 -07:00
Daniel Supernault
207742479c
Update changelog 2022-01-17 17:12:15 -07:00
Daniel Supernault
1c20d6960a
Update MediaStorageService, fix reremote avatar bug 2022-01-17 17:11:16 -07:00
daniel
ea17d763e8
Merge pull request #3161 from pixelfed/staging
Fix typo in ApiV1Controller
2022-01-14 17:27:48 -07:00
Daniel Supernault
350585d316
Fix typo in ApiV1Controller 2022-01-14 17:27:21 -07:00
daniel
c1ed560394
Merge pull request #3147 from pixelfed/staging
Staging
2022-01-09 23:54:53 -07:00
Daniel Supernault
1be5925263
Update changelog 2022-01-09 23:54:22 -07:00
Daniel Supernault
8077ded2dd
Bump version to 0.11.2 2022-01-09 23:54:05 -07:00
daniel
89e39ff699
Merge pull request #3146 from pixelfed/staging
Staging
2022-01-09 23:30:05 -07:00
Daniel Supernault
c68e18aecf
Update changelog 2022-01-09 23:27:57 -07:00
Daniel Supernault
e61dc66a36
Update compiled assets 2022-01-09 23:27:13 -07:00
Daniel Supernault
c8a667f20e
Update SearchApiV2Service, resolve remote queries 2022-01-09 23:15:45 -07:00
daniel
a62d1a1e23
Merge pull request #3145 from pixelfed/staging
Staging
2022-01-09 20:50:51 -07:00
Daniel Supernault
9030c77800
Update changelog 2022-01-09 20:50:21 -07:00
Daniel Supernault
de42d84c11
Update ApiV1Controller, fix reblogs endpoints 2022-01-09 20:49:34 -07:00
Daniel Supernault
d6d99385db
Update ApiV1Controller, fix favourites endpoints 2022-01-09 20:33:43 -07:00
daniel
f543073778
Merge pull request #3144 from pixelfed/staging
Staging
2022-01-09 20:21:46 -07:00
Daniel Supernault
5de6d8accb
Update changelog 2022-01-09 20:20:38 -07:00
Daniel Supernault
6fc0dcb34d
Update ApiV1Controller, fix home timeline entities 2022-01-09 20:18:51 -07:00
daniel
535a0e63ab
Merge pull request #3143 from pixelfed/staging
Staging
2022-01-09 19:33:41 -07:00
Daniel Supernault
80e389b6e6
Update changelog 2022-01-09 19:33:12 -07:00
Daniel Supernault
dcb7ba9c7a
Update PublicApiController, fix public timeline endpoint 2022-01-09 19:32:41 -07:00
Daniel Supernault
80c7def3df
Update ApiV1Controller, fix public timeline endpoint 2022-01-09 19:30:53 -07:00
daniel
b11c77ccb5
Merge pull request #3142 from pixelfed/staging
Staging
2022-01-09 18:20:31 -07:00
Daniel Supernault
7aea7e80e6
Update changelog 2022-01-09 18:19:53 -07:00
Daniel Supernault
dc743aebd0
Update compiled assets 2022-01-09 18:19:44 -07:00
Daniel Supernault
e724633e0f
Update components, fix api endpoints. Fixes #3138 2022-01-09 18:18:25 -07:00
daniel
aa4daa2cf5
Merge pull request #3141 from pixelfed/staging
Staging
2022-01-09 15:42:26 -07:00
Daniel Supernault
f4150df593
Update changelog 2022-01-09 15:41:49 -07:00
Daniel Supernault
a6261221ad
Update ApiV1Controller, fix version on instance endpoint 2022-01-09 15:40:28 -07:00
Daniel Supernault
4ab99d66d1
Update FederationController 2022-01-09 15:09:37 -07:00
daniel
40a96a33f0
Merge pull request #3137 from pixelfed/staging
Update compiled assets
2022-01-08 05:47:04 -07:00
Daniel Supernault
93196e57a0
Update compiled assets 2022-01-08 05:46:36 -07:00
daniel
94df59ed1f
Merge pull request #3136 from pixelfed/staging
Update CommentPipeline
2022-01-08 05:02:26 -07:00
Daniel Supernault
4fe421a166
Update ApiV1Controller 2022-01-08 05:01:26 -07:00
Daniel Supernault
1515a9f111
Update CommentPipeline 2022-01-08 04:59:28 -07:00
daniel
a061e04bbb
Merge pull request #3135 from pixelfed/staging
Update ApiV1Controller, fix account settings bug
2022-01-08 04:06:57 -07:00
Daniel Supernault
a578035bbc
Update AccountService 2022-01-08 04:03:45 -07:00
Daniel Supernault
08246f2482
Update ApiV1Controller, fix account settings bug 2022-01-08 04:02:14 -07:00
daniel
f772925d70
Merge pull request #3134 from pixelfed/staging
Staging
2022-01-08 01:59:01 -07:00
Daniel Supernault
75a44fdc2f
Update compiled assets 2022-01-08 01:57:51 -07:00
Daniel Supernault
f5402de74d
Update i18n strings 2022-01-08 01:52:56 -07:00
Daniel Supernault
699cc3faad
Add i18n strings 2022-01-08 01:52:28 -07:00
Daniel Supernault
da6312c2b0
Update ApiV1Controller 2022-01-08 01:51:24 -07:00
daniel
4efd0b3d91
Merge pull request #3133 from pixelfed/staging
Staging
2022-01-07 22:10:29 -07:00
Daniel Supernault
32bbcc12c3
Update web routes 2022-01-07 22:09:15 -07:00
Daniel Supernault
eeaf0f9311
Update StatusService 2022-01-07 19:59:29 -07:00
Daniel Supernault
0c1a0fa55b
Update PublicApiController 2022-01-07 19:47:21 -07:00
daniel
639c7117f4
Merge pull request #3129 from pixelfed/staging
Staging
2022-01-06 01:17:09 -07:00
Daniel Supernault
24fbdf0afb
Update compiled assets 2022-01-06 01:16:06 -07:00
Daniel Supernault
6d01466239
Update changelog 2022-01-06 01:13:43 -07:00
Daniel Supernault
6f1b02456f
Update AccountController, refresh RelationshipService on mute/blocks 2022-01-06 01:05:11 -07:00
Daniel Supernault
cdf78e23a4
Update ApiV1Controller, improve instance endpoint 2022-01-05 23:37:27 -07:00
Daniel Supernault
ff211dbec5
Update RestrictedNames 2022-01-05 22:28:39 -07:00
Daniel Supernault
e825b07d88
Update ApiV1Controller 2022-01-05 21:40:07 -07:00
Daniel Supernault
31d9e5fb9e
Update changelog 2022-01-05 21:36:26 -07:00
Daniel Supernault
46485426ea
Update ApiV1Controller, add mastoapi strict mode 2022-01-05 21:36:02 -07:00
daniel
2f83d953c2
Merge pull request #3128 from pixelfed/staging
Update compiled assets
2022-01-05 21:09:46 -07:00
Daniel Supernault
1f70e248ff
Update compiled assets 2022-01-05 21:08:37 -07:00
daniel
b34d97d6b7
Merge pull request #3127 from pixelfed/staging
Staging
2022-01-05 19:56:35 -07:00
Daniel Supernault
04c5b79029
Update changelog 2022-01-05 19:47:00 -07:00
Daniel Supernault
861a2d36df
Update PublicApiController, enforce only_media on accountStatuses method. Fixes #3105 2022-01-05 19:31:49 -07:00
daniel
e23d24d3cf
Merge pull request #3126 from pixelfed/staging
Staging
2022-01-05 19:18:05 -07:00
Daniel Supernault
9169d40ce0
Update compiled assets 2022-01-05 19:07:20 -07:00
Daniel Supernault
5bb6ebaec0
Update i18n strings 2022-01-05 19:02:31 -07:00
daniel
21dabfb85b
Merge pull request #3114 from pixelfed/l10n_staging
New Crowdin updates
2022-01-05 18:51:55 -07:00
daniel
3ee699ba3f
Merge pull request #3122 from idanoo/hashtag_unique_constraint_fix
Fix for firstOrCreate failing hashtags with case differences on name column
2022-01-05 18:44:32 -07:00
daniel
854f858543
Merge pull request #3121 from inthreedee/patch-1
Avoid upscaling small images
2022-01-05 18:37:13 -07:00
daniel
c43f9ab7a4
Update app/Util/Media/Image.php
Co-authored-by: Daniel Mason <daniel@m2.nz>
2022-01-05 18:36:02 -07:00
daniel
e2e1a1b2c3
Merge pull request #3124 from pixelfed/staging
Staging
2022-01-05 18:34:50 -07:00
Daniel Supernault
fac8f2c660
Update changelog 2022-01-05 18:32:57 -07:00
Daniel Supernault
fcabc9be02
Fix Direct Message conversations endpoint on postgres instances 2022-01-05 18:30:58 -07:00
Daniel Supernault
ddf41dc347
Update StoryController, add postgres bug fix 2022-01-05 18:17:05 -07:00
Daniel Mason
9cc18eb82a Fix for firstOrCreate failing hashtags with case differences on name 2022-01-05 19:45:19 +13:00
daniel
db058390c3 New translations web.php (Montenegrin (Latin))
[ci skip]
2022-01-04 19:50:03 -07:00
daniel
63e7248ccc New translations web.php (Bosnian)
[ci skip]
2022-01-04 19:50:02 -07:00
daniel
9943e4f1cf New translations web.php (Hindi)
[ci skip]
2022-01-04 19:50:01 -07:00
daniel
5184aca09a New translations web.php (Croatian)
[ci skip]
2022-01-04 19:49:59 -07:00
daniel
d064a5de0d New translations web.php (Macedonian)
[ci skip]
2022-01-04 19:49:58 -07:00
daniel
b1d2e9cf59 New translations web.php (Basque)
[ci skip]
2022-01-04 19:49:57 -07:00
daniel
461956014d New translations web.php (Basque)
[ci skip]
2022-01-03 19:37:53 -07:00
Jonathan
27b715cb35
Avoid upscaling small images 2022-01-03 17:46:10 -05:00
Daniel Supernault
454b4e21dd
Update Services, fix mastoapi compat 2022-01-03 02:31:03 -07:00
Daniel Supernault
85fc9dd0a8
Update PublicApiController, fix accountStatuses pagination operator 2022-01-03 00:59:39 -07:00
Daniel Supernault
c32fde22e7
Add default header 2022-01-03 00:55:13 -07:00
Daniel Supernault
85e4be8172
Update AccountService, add getMastodon method for mastoapi compatibility 2022-01-03 00:53:15 -07:00
daniel
5fc83beb2c
Merge pull request #3117 from pixelfed/staging
Staging
2022-01-03 00:00:14 -07:00
Daniel Supernault
6667f4f1d7
Update changelog 2022-01-02 23:59:20 -07:00
Daniel Supernault
846ce395d5
Update compiled assets 2022-01-02 23:59:08 -07:00
Daniel Supernault
36a129fe89
Update StatusService, add getMastodon method for mastoapi compatibility 2022-01-02 23:47:08 -07:00
Daniel Supernault
772352903b
Update web routes 2022-01-02 23:46:17 -07:00
Daniel Supernault
a64aef6726
Update Profile model, cast last_status_at as timestamp 2022-01-02 23:27:47 -07:00
Daniel Supernault
bdc3ab17dc
Add migration 2022-01-02 23:27:05 -07:00
Daniel Supernault
89206d6e1f
Update mastoapi transformers 2022-01-02 23:26:18 -07:00
daniel
a3c4d29b44
Merge pull request #3116 from pixelfed/staging
Store remote avatars locally if S3 not enabled
2022-01-02 21:39:46 -07:00
Daniel Supernault
41cc741be6
Add avatar:sync command 2022-01-02 21:34:15 -07:00
Daniel Supernault
63e44d7c50
Add changelog 2022-01-02 21:32:24 -07:00
Daniel Supernault
b4bd0400c2
Store remote avatars locally if S3 not enabled 2022-01-02 21:30:02 -07:00
daniel
026ab3f29a New translations web.php (Persian)
[ci skip]
2022-01-02 19:42:07 -07:00
daniel
27599e0da0 New translations web.php (Slovak)
[ci skip]
2022-01-02 19:42:06 -07:00
daniel
028b67e0fd New translations web.php (Italian)
[ci skip]
2022-01-02 19:42:05 -07:00
daniel
8ab69dbe4c New translations web.php (Vietnamese)
[ci skip]
2022-01-01 19:29:22 -07:00
daniel
4d04f03d34 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-31 19:32:06 -07:00
daniel
a411ec813f New translations web.php (Slovak)
[ci skip]
2021-12-31 19:32:06 -07:00
daniel
e1d2d91b76 New translations web.php (German)
[ci skip]
2021-12-31 19:32:05 -07:00
daniel
91b2d3c2db New translations web.php (Czech)
[ci skip]
2021-12-31 19:32:04 -07:00
daniel
2072c44d8b New translations web.php (Arabic)
[ci skip]
2021-12-31 19:32:03 -07:00
daniel
039328a32e
Merge pull request #3113 from pixelfed/staging
Update exception handler
2021-12-31 01:15:44 -07:00
Daniel Supernault
1a979bee5c
Update exception handler 2021-12-31 01:14:50 -07:00
Daniel Supernault
f4e12c42b3
Update exception handler 2021-12-31 01:12:33 -07:00
daniel
4e27fee344
Merge pull request #3112 from pixelfed/staging
Staging
2021-12-31 00:58:44 -07:00
Daniel Supernault
c481e473d8
Update Exception handler 2021-12-31 00:58:16 -07:00
Daniel Supernault
d820669bd1
Update StatusController, change param signature 2021-12-31 00:58:00 -07:00
daniel
9d8168cf20
Merge pull request #3110 from pixelfed/staging
Staging
2021-12-30 23:20:40 -07:00
Daniel Supernault
0acd79742c
Update changelog 2021-12-30 23:20:12 -07:00
Daniel Supernault
415826f253
Update ApiV1Controller, fix illegal operator bug by setting default min_id 2021-12-30 23:19:38 -07:00
daniel
93c32b0771
Merge pull request #3109 from pixelfed/staging
Staging
2021-12-30 23:13:04 -07:00
Daniel Supernault
35e2cf1f6b
Update changelog 2021-12-30 23:12:16 -07:00
Daniel Supernault
e5f8f3441c
Update AccountService, fix json casting 2021-12-30 23:11:46 -07:00
daniel
8957e6e3c0
Merge pull request #3108 from pixelfed/staging
Update RemoteAvatarFetch job
2021-12-30 21:06:24 -07:00
Daniel Supernault
92bc28456d
Update RemoteAvatarFetch job 2021-12-30 21:05:50 -07:00
daniel
dc603062fa
Merge pull request #3106 from pixelfed/staging
Staging
2021-12-30 20:31:24 -07:00
Daniel Supernault
59ffa22e08
Update compiled assets 2021-12-30 20:31:12 -07:00
daniel
20e11e175a
Merge pull request #3107 from pixelfed/l10n_staging
New Crowdin updates
2021-12-30 20:24:58 -07:00
daniel
cd3f8f1cce New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-30 19:33:25 -07:00
daniel
faabe3e708 New translations web.php (Portuguese)
[ci skip]
2021-12-30 19:33:23 -07:00
daniel
3baa275292 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-30 19:33:22 -07:00
daniel
86cacf7b52 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-30 19:33:18 -07:00
Daniel Supernault
bc91909ca6
Update i18n strings 2021-12-30 18:45:30 -07:00
daniel
fab76db993
Merge pull request #3098 from pixelfed/l10n_staging
New Crowdin updates
2021-12-30 18:43:08 -07:00
daniel
03b4b8a81e New translations web.php (Italian)
[ci skip]
2021-12-30 17:31:39 -07:00
daniel
318062cc34 New translations web.php (Hungarian)
[ci skip]
2021-12-30 17:31:38 -07:00
daniel
11ffe5973b New translations web.php (Finnish)
[ci skip]
2021-12-30 17:31:37 -07:00
daniel
ac9e7eac6d New translations web.php (Basque)
[ci skip]
2021-12-30 17:31:35 -07:00
daniel
1a33a91eff New translations web.php (German)
[ci skip]
2021-12-30 17:31:35 -07:00
daniel
d157a7608f New translations web.php (Czech)
[ci skip]
2021-12-30 17:31:33 -07:00
daniel
efb60698c9 New translations web.php (Spanish)
[ci skip]
2021-12-30 17:31:30 -07:00
daniel
1efd420f33 New translations web.php (French)
[ci skip]
2021-12-30 17:31:29 -07:00
daniel
e1fa9d9a87 New translations web.php (Polish)
[ci skip]
2021-12-30 17:31:28 -07:00
daniel
73c7e0d446 New translations web.php (Russian)
[ci skip]
2021-12-30 17:31:27 -07:00
daniel
1df0669c09 New translations web.php (Welsh)
[ci skip]
2021-12-30 17:31:25 -07:00
daniel
38784a344f New translations web.php (Indonesian)
[ci skip]
2021-12-30 17:31:23 -07:00
daniel
6b76e85ff5 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-30 17:31:22 -07:00
daniel
9ab11f49b6 New translations web.php (Galician)
[ci skip]
2021-12-30 17:31:21 -07:00
daniel
9eca5de3c7 New translations web.php (Portuguese)
[ci skip]
2021-12-30 17:31:20 -07:00
daniel
37555b4da5 New translations web.php (Vietnamese)
[ci skip]
2021-12-30 17:31:19 -07:00
daniel
c2eddbd4a1 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-30 17:31:18 -07:00
daniel
fb02f1e8c4 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-30 17:31:14 -07:00
daniel
c70a4c786e New translations web.php (Romanian)
[ci skip]
2021-12-30 17:31:13 -07:00
daniel
9b55e48dca New translations web.php (Romanian)
[ci skip]
2021-12-30 12:48:14 -07:00
daniel
ff7c7915f9 New translations web.php (Romanian)
[ci skip]
2021-12-30 11:40:36 -07:00
daniel
37a57e5a46 New translations web.php (Basque)
[ci skip]
2021-12-30 10:39:06 -07:00
daniel
4b982a1423 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-30 02:07:25 -07:00
daniel
6657fe6513
Merge pull request #3104 from pixelfed/staging
Update compiled assets
2021-12-29 20:54:11 -07:00
Daniel Supernault
ed190bc7ab
Update changelog 2021-12-29 20:53:53 -07:00
daniel
69bb55e9d7 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-29 19:32:31 -07:00
Daniel Supernault
4dd6caf02d
Update compiled assets 2021-12-29 18:29:28 -07:00
daniel
fa1cf25c70 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-29 18:27:09 -07:00
daniel
4c0626f428 New translations web.php (Polish)
[ci skip]
2021-12-29 13:24:57 -07:00
daniel
68e82c0aea New translations web.php (Welsh)
[ci skip]
2021-12-29 12:15:50 -07:00
daniel
c0f04a0dfb New translations web.php (Indonesian)
[ci skip]
2021-12-29 12:15:49 -07:00
daniel
ed183ade4b New translations web.php (Galician)
[ci skip]
2021-12-29 12:15:48 -07:00
daniel
4a34671261 New translations web.php (Finnish)
[ci skip]
2021-12-29 10:58:40 -07:00
daniel
88e48df000 New translations web.php (Finnish)
[ci skip]
2021-12-29 10:00:38 -07:00
daniel
853d64e91c New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-29 05:54:45 -07:00
daniel
970fa7db62 New translations web.php (Spanish)
[ci skip]
2021-12-29 04:52:58 -07:00
daniel
18b5070206 New translations web.php (Italian)
[ci skip]
2021-12-29 03:31:45 -07:00
daniel
fd8a0a603c New translations web.php (Hungarian)
[ci skip]
2021-12-29 03:31:44 -07:00
daniel
012d81b67a New translations web.php (Czech)
[ci skip]
2021-12-29 03:31:43 -07:00
daniel
50f59c82ee New translations web.php (French)
[ci skip]
2021-12-29 03:31:42 -07:00
daniel
c047237f33 New translations web.php (German)
[ci skip]
2021-12-29 02:17:05 -07:00
daniel
5646fbf878 New translations web.php (Czech)
[ci skip]
2021-12-29 02:17:04 -07:00
daniel
8c917e8996
Merge pull request #3101 from pixelfed/staging
Staging
2021-12-29 02:10:13 -07:00
Daniel Supernault
cc522ccf3f
Update changelog 2021-12-29 02:09:30 -07:00
Daniel Supernault
8596451066
Update compiled assets 2021-12-29 02:07:47 -07:00
Daniel Supernault
b95aec1208
Enable network timeline by default 2021-12-29 02:04:39 -07:00
daniel
c74b45da7a
Merge pull request #3100 from pixelfed/staging
Staging
2021-12-29 01:48:02 -07:00
Daniel Supernault
6913366063
Update changelog 2021-12-29 01:47:31 -07:00
Daniel Supernault
50e845d606
Update compiled assets 2021-12-29 01:47:15 -07:00
Daniel Supernault
10b6058cc0
Update DiscoverController, add yearly option and increase limit from 15 to 30 posts 2021-12-29 01:46:29 -07:00
daniel
2df960ac7a
Merge pull request #3099 from pixelfed/staging
Update RegisterController
2021-12-29 00:51:40 -07:00
Daniel Supernault
af68872a81
Update compiled assets 2021-12-29 00:51:22 -07:00
Daniel Supernault
e503a8da85
Update RegisterController 2021-12-29 00:38:08 -07:00
daniel
f6426cb9bd
Merge pull request #3097 from pixelfed/staging
Staging
2021-12-29 00:17:20 -07:00
Daniel Supernault
a259c6919f
Update changelog 2021-12-29 00:17:08 -07:00
Daniel Supernault
ae1b50d84b
Update compiled assets 2021-12-29 00:17:03 -07:00
Daniel Supernault
b37c805172
Update AdminReportController 2021-12-29 00:15:01 -07:00
daniel
44d877d670 New translations web.php (Vietnamese)
[ci skip]
2021-12-29 00:07:52 -07:00
daniel
bc69463492 New translations web.php (Russian)
[ci skip]
2021-12-29 00:07:51 -07:00
Daniel Supernault
30c1de9796
Update i18n strings 2021-12-29 00:07:19 -07:00
Daniel Supernault
745c35807c
Update FederationController, increase webfinger cache ttl from 12 hours to 14 days 2021-12-28 23:49:56 -07:00
daniel
8db6a79de9
Merge pull request #3095 from pixelfed/l10n_staging
New Crowdin updates
2021-12-28 23:43:33 -07:00
daniel
41758e37fb
Merge pull request #3096 from pixelfed/staging
Staging
2021-12-28 23:04:17 -07:00
Daniel Supernault
1d74070bf0
Update changelog 2021-12-28 23:04:02 -07:00
daniel
3c0fbcac69 New translations web.php (Hebrew)
[ci skip]
2021-12-28 23:02:38 -07:00
Daniel Supernault
f2467e71e9
Update compiled assets 2021-12-28 23:01:25 -07:00
Daniel Supernault
5b5d1b2bf2
Update timeline copy 2021-12-28 22:56:06 -07:00
daniel
f2a505b71d New translations web.php (Greek)
[ci skip]
2021-12-28 22:04:38 -07:00
daniel
c12d866be6 New translations web.php (Japanese)
[ci skip]
2021-12-28 20:17:17 -07:00
daniel
4f572af75c New translations web.php (Indonesian)
[ci skip]
2021-12-28 20:17:05 -07:00
daniel
037db34e67 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-28 20:17:04 -07:00
daniel
849c0fea2b New translations web.php (Portuguese)
[ci skip]
2021-12-28 20:17:03 -07:00
daniel
bc3864d38d New translations web.php (Chinese Simplified)
[ci skip]
2021-12-28 20:17:02 -07:00
daniel
3bdfc94244 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-28 20:16:58 -07:00
daniel
711c571a3b
Merge pull request #3094 from pixelfed/staging
Update compiled assets
2021-12-28 19:45:36 -07:00
Daniel Supernault
c9d35f1003
Update compiled assets 2021-12-28 19:45:16 -07:00
daniel
d2bc455194
Merge pull request #3093 from pixelfed/staging
Staging
2021-12-28 19:38:51 -07:00
Daniel Supernault
2b11205a2c
Update compiled assets 2021-12-28 19:37:57 -07:00
Daniel Supernault
f3a66fff61
Update experiment config, force enabled single page app 2021-12-28 19:37:30 -07:00
Daniel Supernault
e13959ae45
Update Timeline, add new ui promo in timelines that can be hidden using localstorage 2021-12-28 19:35:37 -07:00
daniel
cc63d3d1f7
Merge pull request #3092 from pixelfed/staging
Staging
2021-12-28 19:12:44 -07:00
Daniel Supernault
07a20e578c
Update compiled assets 2021-12-28 19:12:30 -07:00
Daniel Supernault
1344de4cf2
Update i18n strings 2021-12-28 19:09:56 -07:00
daniel
e567dfd9a0
Merge pull request #3090 from pixelfed/l10n_staging
New Crowdin updates
2021-12-28 19:09:03 -07:00
daniel
a95c5ce8e1 New translations web.php (Occitan)
[ci skip]
2021-12-28 19:08:29 -07:00
daniel
d623059052 New translations web.php (Dutch)
[ci skip]
2021-12-28 19:08:28 -07:00
daniel
8d5b584f10 New translations web.php (Korean)
[ci skip]
2021-12-28 19:08:27 -07:00
daniel
bbb82569f0 New translations web.php (Japanese)
[ci skip]
2021-12-28 19:08:26 -07:00
daniel
8fa6d65102 New translations web.php (Italian)
[ci skip]
2021-12-28 19:08:25 -07:00
daniel
5c84fcdfa4 New translations web.php (Hungarian)
[ci skip]
2021-12-28 19:08:24 -07:00
daniel
9dea452844 New translations web.php (Hebrew)
[ci skip]
2021-12-28 19:08:24 -07:00
daniel
b7fcfe51bd New translations web.php (Finnish)
[ci skip]
2021-12-28 19:08:23 -07:00
daniel
e0a0a5f949 New translations web.php (Norwegian)
[ci skip]
2021-12-28 19:08:21 -07:00
daniel
cdb9e344ba New translations web.php (Basque)
[ci skip]
2021-12-28 19:08:21 -07:00
daniel
f9ea5eb227 New translations web.php (German)
[ci skip]
2021-12-28 19:08:19 -07:00
daniel
a7fd7d2834 New translations web.php (Danish)
[ci skip]
2021-12-28 19:08:19 -07:00
daniel
8ada9f12aa New translations web.php (Czech)
[ci skip]
2021-12-28 19:08:18 -07:00
daniel
5413ad8145 New translations web.php (Catalan)
[ci skip]
2021-12-28 19:08:17 -07:00
daniel
fd015096d8 New translations web.php (Arabic)
[ci skip]
2021-12-28 19:08:16 -07:00
daniel
aeb4555078 New translations web.php (Afrikaans)
[ci skip]
2021-12-28 19:08:15 -07:00
daniel
ecd73b77d7 New translations web.php (Spanish)
[ci skip]
2021-12-28 19:08:14 -07:00
daniel
6f374f80da New translations web.php (Greek)
[ci skip]
2021-12-28 19:08:13 -07:00
daniel
1d21922de3 New translations web.php (French)
[ci skip]
2021-12-28 19:08:12 -07:00
daniel
9b6630c86e New translations web.php (Polish)
[ci skip]
2021-12-28 19:08:11 -07:00
daniel
780d07c32f New translations web.php (Russian)
[ci skip]
2021-12-28 19:08:10 -07:00
daniel
68d76e9ac8 New translations web.php (Scottish Gaelic)
[ci skip]
2021-12-28 19:08:09 -07:00
daniel
f46c5e033a New translations web.php (Welsh)
[ci skip]
2021-12-28 19:08:08 -07:00
daniel
08c2a842cd New translations web.php (Bengali)
[ci skip]
2021-12-28 19:08:07 -07:00
daniel
f79905ca40 New translations web.php (Persian)
[ci skip]
2021-12-28 19:08:06 -07:00
daniel
bb27ae4794 New translations web.php (Indonesian)
[ci skip]
2021-12-28 19:08:05 -07:00
daniel
b43fb88a81 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-28 19:08:04 -07:00
daniel
64aacd5e9d New translations web.php (Galician)
[ci skip]
2021-12-28 19:08:03 -07:00
daniel
8e0f9ddb93 New translations web.php (Portuguese)
[ci skip]
2021-12-28 19:08:02 -07:00
daniel
454fbd8a24 New translations web.php (Vietnamese)
[ci skip]
2021-12-28 19:08:01 -07:00
daniel
d00dc1c0a7 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-28 19:08:00 -07:00
daniel
fdd889dcff New translations web.php (Ukrainian)
[ci skip]
2021-12-28 19:07:59 -07:00
daniel
c660ff9fbe New translations web.php (Turkish)
[ci skip]
2021-12-28 19:07:58 -07:00
daniel
047d4bc07c New translations web.php (Swedish)
[ci skip]
2021-12-28 19:07:57 -07:00
daniel
64fb4d1713 New translations web.php (Serbian (Cyrillic))
[ci skip]
2021-12-28 19:07:56 -07:00
daniel
cea7dd291b New translations web.php (Slovak)
[ci skip]
2021-12-28 19:07:55 -07:00
daniel
23f1f81c21 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-28 19:07:54 -07:00
daniel
3a6c852724 New translations web.php (Romanian)
[ci skip]
2021-12-28 19:07:53 -07:00
Daniel Supernault
802d9026c6
Update changelog 2021-12-28 19:06:22 -07:00
Daniel Supernault
b042661df2
Update compiled assets 2021-12-28 19:06:08 -07:00
Daniel Supernault
e95718fe6f
Update i18n strings 2021-12-28 19:04:52 -07:00
Daniel Supernault
e46b9b2798
Add i18n string 2021-12-28 19:02:29 -07:00
Daniel Supernault
b00a453b99
Update status api, autolink caption before returning response 2021-12-28 18:26:01 -07:00
daniel
e8dc131042 New translations web.php (French)
[ci skip]
2021-12-28 12:22:28 -07:00
daniel
e0bd18dc69 New translations web.php (Welsh)
[ci skip]
2021-12-28 11:07:27 -07:00
daniel
142c964557 New translations web.php (Basque)
[ci skip]
2021-12-28 11:07:26 -07:00
daniel
914ba6f41f New translations web.php (Welsh)
[ci skip]
2021-12-28 10:10:25 -07:00
daniel
a3341b166f New translations web.php (Basque)
[ci skip]
2021-12-28 10:10:24 -07:00
daniel
57949fa68b New translations web.php (Czech)
[ci skip]
2021-12-28 10:10:22 -07:00
daniel
23f625669d New translations web.php (Czech)
[ci skip]
2021-12-28 08:37:59 -07:00
daniel
4bb3d84fcb New translations web.php (Vietnamese)
[ci skip]
2021-12-27 20:52:57 -07:00
daniel
4890528510 New translations web.php (Vietnamese)
[ci skip]
2021-12-27 19:56:48 -07:00
daniel
936d0b8f95 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-27 16:59:10 -07:00
daniel
244a6c3fae New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-27 16:59:08 -07:00
daniel
926e6b15b1 New translations web.php (Portuguese)
[ci skip]
2021-12-27 16:58:52 -07:00
daniel
af573f2d99 New translations web.php (Hungarian)
[ci skip]
2021-12-27 14:22:40 -07:00
daniel
f7438ef022 New translations web.php (Hungarian)
[ci skip]
2021-12-27 13:23:41 -07:00
daniel
c979154b97 New translations web.php (Scottish Gaelic)
[ci skip]
2021-12-26 14:50:21 -07:00
daniel
63d7e7fccd New translations web.php (Arabic)
[ci skip]
2021-12-26 13:51:45 -07:00
daniel
b56813360d New translations web.php (Polish)
[ci skip]
2021-12-26 12:25:56 -07:00
daniel
b4030c2d82 New translations web.php (Hebrew)
[ci skip]
2021-12-26 11:25:37 -07:00
daniel
cdfd53ec27 New translations web.php (Russian)
[ci skip]
2021-12-26 10:11:59 -07:00
daniel
866ba80f23 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-26 09:06:58 -07:00
daniel
d3da149b17 New translations web.php (Vietnamese)
[ci skip]
2021-12-26 09:06:57 -07:00
daniel
088ec7e266 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-26 07:46:29 -07:00
daniel
7a0ffddcd7 New translations web.php (Basque)
[ci skip]
2021-12-26 06:32:26 -07:00
daniel
9cad926a51 New translations web.php (Occitan)
[ci skip]
2021-12-26 05:28:27 -07:00
daniel
471474b3fd New translations web.php (French)
[ci skip]
2021-12-26 05:28:26 -07:00
daniel
59c226bd73
Merge pull request #3091 from pixelfed/staging
Staging
2021-12-26 05:01:13 -07:00
Daniel Supernault
a994ec7be7
Update changelog 2021-12-26 04:59:07 -07:00
Daniel Supernault
ff7ee3bd62
Update Webfinger, fixes #3050 2021-12-26 04:58:19 -07:00
daniel
e8b2aa06c6 New translations web.php (Slovak)
[ci skip]
2021-12-26 04:26:31 -07:00
daniel
37e081cc80 New translations web.php (Japanese)
[ci skip]
2021-12-26 04:26:30 -07:00
daniel
4ece158c22 New translations web.php (German)
[ci skip]
2021-12-26 04:26:27 -07:00
daniel
735c820cdc New translations web.php (Portuguese)
[ci skip]
2021-12-26 04:26:22 -07:00
daniel
a8443729f2 New translations web.php (Indonesian)
[ci skip]
2021-12-26 04:26:19 -07:00
daniel
a9f9b02108 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-26 04:26:16 -07:00
daniel
06e9c0f88e New translations web.php (Chinese Simplified)
[ci skip]
2021-12-26 04:26:16 -07:00
daniel
fe0e0388cb New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-26 04:26:14 -07:00
Daniel Supernault
469d49d832
Update site config, increase ttl and enable SPA by default 2021-12-26 03:46:15 -07:00
daniel
803cc058b2
Merge pull request #3089 from pixelfed/staging
Staging
2021-12-26 03:30:48 -07:00
Daniel Supernault
d9551fc3b7
Update compiled assets 2021-12-26 03:30:15 -07:00
Daniel Supernault
3c5bf61861
Update i18n strings 2021-12-26 03:27:10 -07:00
daniel
2b1809e478
Merge pull request #3088 from pixelfed/l10n_staging
New Crowdin updates
2021-12-26 03:25:47 -07:00
daniel
30896e0380 New translations web.php (Slovak)
[ci skip]
2021-12-26 03:25:10 -07:00
daniel
64448bb0b0 New translations web.php (Norwegian)
[ci skip]
2021-12-26 03:25:09 -07:00
daniel
d0d85b342a New translations web.php (Dutch)
[ci skip]
2021-12-26 03:25:08 -07:00
daniel
cb51d304ef New translations web.php (Korean)
[ci skip]
2021-12-26 03:25:03 -07:00
daniel
22719874aa New translations web.php (Japanese)
[ci skip]
2021-12-26 03:25:02 -07:00
daniel
7c4107d134 New translations web.php (Italian)
[ci skip]
2021-12-26 03:25:01 -07:00
daniel
7ab7881f60 New translations web.php (Hungarian)
[ci skip]
2021-12-26 03:25:01 -07:00
daniel
ee682a946c New translations web.php (Hebrew)
[ci skip]
2021-12-26 03:24:59 -07:00
daniel
7dc3c41cbd New translations web.php (Polish)
[ci skip]
2021-12-26 03:24:58 -07:00
daniel
9e9436f58e New translations web.php (Finnish)
[ci skip]
2021-12-26 03:24:57 -07:00
daniel
46f273fdaa New translations web.php (German)
[ci skip]
2021-12-26 03:24:56 -07:00
daniel
0437008a03 New translations web.php (Danish)
[ci skip]
2021-12-26 03:24:55 -07:00
daniel
1fd96bd20b New translations web.php (Czech)
[ci skip]
2021-12-26 03:24:55 -07:00
daniel
1a24810148 New translations web.php (Catalan)
[ci skip]
2021-12-26 03:24:54 -07:00
daniel
661a422985 New translations web.php (Arabic)
[ci skip]
2021-12-26 03:24:53 -07:00
daniel
3fd6605e9b New translations web.php (Afrikaans)
[ci skip]
2021-12-26 03:24:52 -07:00
daniel
70f8cde66e New translations web.php (Spanish)
[ci skip]
2021-12-26 03:24:51 -07:00
daniel
eefb981d82 New translations web.php (Greek)
[ci skip]
2021-12-26 03:24:50 -07:00
daniel
4c85838c97 New translations web.php (French)
[ci skip]
2021-12-26 03:24:49 -07:00
daniel
e10605c921 New translations web.php (Portuguese)
[ci skip]
2021-12-26 03:24:48 -07:00
daniel
79d3042015 New translations web.php (Serbian (Cyrillic))
[ci skip]
2021-12-26 03:24:48 -07:00
daniel
30d3ba3e35 New translations web.php (Occitan)
[ci skip]
2021-12-26 03:24:47 -07:00
daniel
0df994ea56 New translations web.php (Persian)
[ci skip]
2021-12-26 03:24:46 -07:00
daniel
8f6511094c New translations web.php (Bengali)
[ci skip]
2021-12-26 03:24:45 -07:00
daniel
55953fac2c New translations web.php (Scottish Gaelic)
[ci skip]
2021-12-26 03:24:44 -07:00
daniel
bb48d25977 New translations web.php (Basque)
[ci skip]
2021-12-26 03:24:43 -07:00
daniel
bc9e312a02 New translations web.php (Welsh)
[ci skip]
2021-12-26 03:24:42 -07:00
daniel
88234b8b14 New translations web.php (Indonesian)
[ci skip]
2021-12-26 03:24:41 -07:00
daniel
224c4f568c New translations web.php (Russian)
[ci skip]
2021-12-26 03:24:40 -07:00
daniel
9a48c77ce8 New translations web.php (Galician)
[ci skip]
2021-12-26 03:24:40 -07:00
daniel
d81c9a15ad New translations web.php (Vietnamese)
[ci skip]
2021-12-26 03:24:39 -07:00
daniel
11cf631506 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-26 03:24:38 -07:00
daniel
c01ef6b345 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-26 03:24:37 -07:00
daniel
dc7f3f6132 New translations web.php (Ukrainian)
[ci skip]
2021-12-26 03:24:36 -07:00
daniel
44ee6f1801 New translations web.php (Turkish)
[ci skip]
2021-12-26 03:24:35 -07:00
daniel
1e44d4041f New translations web.php (Swedish)
[ci skip]
2021-12-26 03:24:34 -07:00
daniel
e18866fc8d New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-26 03:24:34 -07:00
daniel
d0acc69461 New translations web.php (Romanian)
[ci skip]
2021-12-26 03:24:33 -07:00
Daniel Supernault
5dd7b48b29
Add i18n string 2021-12-26 03:23:59 -07:00
daniel
eeae69b8cb
Merge pull request #3082 from pixelfed/l10n_staging
New Crowdin updates
2021-12-26 03:21:01 -07:00
daniel
d2d6226677 New translations web.php (Slovak)
[ci skip]
2021-12-26 03:01:40 -07:00
daniel
3dc45ee331 New translations web.php (Vietnamese)
[ci skip]
2021-12-26 03:01:39 -07:00
daniel
dadb01176c New translations web.php (Slovak)
[ci skip]
2021-12-26 02:01:00 -07:00
daniel
c96ca13d0a
Merge pull request #3087 from pixelfed/staging
Update compiled assets
2021-12-26 01:02:05 -07:00
Daniel Supernault
182902a553
Update compiled assets 2021-12-26 01:01:36 -07:00
daniel
d518f0e96f New translations web.php (Chinese Simplified)
[ci skip]
2021-12-26 00:45:05 -07:00
daniel
f914d14d97
Merge pull request #3086 from pixelfed/staging
Update compiled assets
2021-12-26 00:26:24 -07:00
Daniel Supernault
e1a071109e
Update compiled assets 2021-12-26 00:26:01 -07:00
daniel
4e7d0c153c
Merge pull request #3085 from pixelfed/staging
Staging
2021-12-26 00:17:16 -07:00
Daniel Supernault
47ebd1e4b1
Update changelog 2021-12-26 00:16:36 -07:00
Daniel Supernault
0837b410b1
Update SPA, rewrite autolink urls to SPA when applicable 2021-12-26 00:16:04 -07:00
Daniel Supernault
5c65e2b761
Update SpaController, add username redirect method 2021-12-26 00:08:18 -07:00
daniel
b4ecee9d7c New translations web.php (Japanese)
[ci skip]
2021-12-25 23:45:56 -07:00
daniel
cd6c916512
Merge pull request #3084 from pixelfed/staging
Update LoginController, bump decayMinutes from 1 to 60
2021-12-25 21:48:15 -07:00
Daniel Supernault
8c2c69c3aa
Update changelog 2021-12-25 21:48:05 -07:00
Daniel Supernault
6bf92bed27
Update LoginController, bump decayMinutes from 1 to 60 2021-12-25 21:47:16 -07:00
daniel
cde17e77a3
Merge pull request #3083 from pixelfed/staging
Update compiled assets
2021-12-25 21:13:34 -07:00
Daniel Supernault
3f511e3a39
Update compiled assets 2021-12-25 21:13:03 -07:00
daniel
ec9d52f8bb New translations web.php (Slovak)
[ci skip]
2021-12-25 21:11:54 -07:00
daniel
5b56b5ebae New translations web.php (Norwegian)
[ci skip]
2021-12-25 21:11:54 -07:00
daniel
9a5619c2dd New translations web.php (Dutch)
[ci skip]
2021-12-25 21:11:53 -07:00
daniel
c66c473f55 New translations web.php (Korean)
[ci skip]
2021-12-25 21:11:52 -07:00
daniel
6d5cb3e166 New translations web.php (Japanese)
[ci skip]
2021-12-25 21:11:51 -07:00
daniel
1e4cb9886f New translations web.php (Italian)
[ci skip]
2021-12-25 21:11:50 -07:00
daniel
f218706d8b New translations web.php (Hungarian)
[ci skip]
2021-12-25 21:11:49 -07:00
daniel
57a9df0a08 New translations web.php (Hebrew)
[ci skip]
2021-12-25 21:11:48 -07:00
daniel
f69c521a3d New translations web.php (Polish)
[ci skip]
2021-12-25 21:11:47 -07:00
daniel
e58e4a50fc New translations web.php (Finnish)
[ci skip]
2021-12-25 21:11:46 -07:00
daniel
264dd55103 New translations web.php (German)
[ci skip]
2021-12-25 21:11:45 -07:00
daniel
20044c2bd4 New translations web.php (Danish)
[ci skip]
2021-12-25 21:11:44 -07:00
daniel
d8ab23cce0 New translations web.php (Czech)
[ci skip]
2021-12-25 21:11:43 -07:00
daniel
64308584cd New translations web.php (Catalan)
[ci skip]
2021-12-25 21:11:42 -07:00
daniel
4a3cc65caa New translations web.php (Arabic)
[ci skip]
2021-12-25 21:11:41 -07:00
daniel
623e4881fb New translations web.php (Afrikaans)
[ci skip]
2021-12-25 21:11:40 -07:00
daniel
88fe848d3a New translations web.php (Spanish)
[ci skip]
2021-12-25 21:11:40 -07:00
daniel
13ffd1b7f6 New translations web.php (Greek)
[ci skip]
2021-12-25 21:11:39 -07:00
daniel
ffb352d2e9 New translations web.php (French)
[ci skip]
2021-12-25 21:11:38 -07:00
daniel
1e84895532 New translations web.php (Portuguese)
[ci skip]
2021-12-25 21:11:37 -07:00
daniel
f004757756 New translations web.php (Serbian (Cyrillic))
[ci skip]
2021-12-25 21:11:36 -07:00
daniel
24db88cd62 New translations web.php (Occitan)
[ci skip]
2021-12-25 21:11:35 -07:00
daniel
fdff3baa37 New translations web.php (Persian)
[ci skip]
2021-12-25 21:11:34 -07:00
daniel
c18063fef2 New translations web.php (Bengali)
[ci skip]
2021-12-25 21:11:33 -07:00
daniel
24d1f059a4 New translations web.php (Scottish Gaelic)
[ci skip]
2021-12-25 21:11:32 -07:00
daniel
9d29193a85 New translations web.php (Basque)
[ci skip]
2021-12-25 21:11:32 -07:00
daniel
f6531ade09 New translations web.php (Welsh)
[ci skip]
2021-12-25 21:11:31 -07:00
daniel
eac09c34e1 New translations web.php (Indonesian)
[ci skip]
2021-12-25 21:11:30 -07:00
daniel
d7bc2914a4 New translations web.php (Russian)
[ci skip]
2021-12-25 21:11:29 -07:00
daniel
9e333d87b9 New translations web.php (Galician)
[ci skip]
2021-12-25 21:11:28 -07:00
daniel
ede6759b54 New translations web.php (Vietnamese)
[ci skip]
2021-12-25 21:11:27 -07:00
daniel
53e187b1d1 New translations web.php (Chinese Simplified)
[ci skip]
2021-12-25 21:11:26 -07:00
daniel
b9139f3a2f New translations web.php (Ukrainian)
[ci skip]
2021-12-25 21:11:25 -07:00
daniel
fe8c50812b New translations web.php (Turkish)
[ci skip]
2021-12-25 21:11:24 -07:00
daniel
fa83d8f955 New translations web.php (Swedish)
[ci skip]
2021-12-25 21:11:23 -07:00
daniel
1ebefcba93 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-25 21:11:22 -07:00
daniel
36c2983a1e New translations web.php (Romanian)
[ci skip]
2021-12-25 21:11:21 -07:00
daniel
9140a19741
Merge pull request #3081 from pixelfed/staging
Staging
2021-12-25 20:13:51 -07:00
Daniel Supernault
aa89075317
Update compiled assets 2021-12-25 20:13:01 -07:00
Daniel Supernault
8be29d295d
Update i18n strings 2021-12-25 20:09:39 -07:00
Daniel Supernault
07c508021e
Add i18n string 2021-12-25 20:08:52 -07:00
daniel
3cccb8e237
Merge pull request #3079 from pixelfed/l10n_staging
New Crowdin updates
2021-12-25 19:55:12 -07:00
daniel
f2511f2d15 New translations web.php (Slovak)
[ci skip]
2021-12-25 19:54:51 -07:00
daniel
ee62f967bd New translations web.php (Portuguese)
[ci skip]
2021-12-25 19:54:40 -07:00
daniel
9f37f574b5 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-25 19:54:29 -07:00
daniel
703d92d9f4 New translations web.php (Occitan)
[ci skip]
2021-12-25 15:37:38 -07:00
daniel
cf9eb563c1 New translations web.php (Persian)
[ci skip]
2021-12-25 11:03:19 -07:00
daniel
6bbc2f78e6 New translations web.php (Persian)
[ci skip]
2021-12-25 09:54:55 -07:00
daniel
55322bb298 New translations web.php (Basque)
[ci skip]
2021-12-25 01:49:42 -07:00
daniel
e33f7ec91e New translations web.php (Hungarian)
[ci skip]
2021-12-24 13:52:23 -07:00
daniel
b70142f8c9 New translations web.php (Turkish)
[ci skip]
2021-12-24 07:49:42 -07:00
daniel
3b9154e5a7 New translations web.php (Persian)
[ci skip]
2021-12-24 06:46:30 -07:00
daniel
2f1c1d53c2 New translations web.php (Welsh)
[ci skip]
2021-12-24 06:46:29 -07:00
daniel
c2d0c2dc42 New translations web.php (Turkish)
[ci skip]
2021-12-24 06:46:28 -07:00
daniel
79089aa0ea New translations web.php (Persian)
[ci skip]
2021-12-24 05:17:44 -07:00
daniel
e522553592 New translations web.php (Persian)
[ci skip]
2021-12-24 04:02:06 -07:00
daniel
5a3bf4ba10 New translations web.php (Turkish)
[ci skip]
2021-12-24 04:02:05 -07:00
daniel
ab3e4386dc New translations web.php (Hungarian)
[ci skip]
2021-12-24 04:02:04 -07:00
daniel
a902edc336 New translations web.php (Hungarian)
[ci skip]
2021-12-24 02:56:20 -07:00
daniel
ffc8529ad3 New translations web.php (Russian)
[ci skip]
2021-12-23 22:26:50 -07:00
daniel
757615572d
Merge pull request #3078 from pixelfed/staging
Add illustrations
2021-12-23 18:54:29 -07:00
Daniel Supernault
b87fc212f0
Add illustrations 2021-12-23 18:53:54 -07:00
daniel
db2a241ce6
Merge pull request #3077 from pixelfed/staging
Staging
2021-12-23 17:41:26 -07:00
Daniel Supernault
0068b0cdc4
Update compiled assets 2021-12-23 17:40:05 -07:00
Daniel Supernault
7bc684e5d1
Update SpaController, persist web language changes 2021-12-23 17:37:08 -07:00
Daniel Supernault
aed570b3f6
Update lang strings 2021-12-23 17:35:10 -07:00
daniel
46b185351f
Merge pull request #3076 from pixelfed/l10n_staging
New Crowdin updates
2021-12-23 17:26:59 -07:00
daniel
689ea50ff5 New translations web.php (Portuguese)
[ci skip]
2021-12-23 17:26:30 -07:00
daniel
bddc271cf5 New translations web.php (German)
[ci skip]
2021-12-23 17:26:14 -07:00
daniel
8cf345a3a0
Merge pull request #3072 from pixelfed/l10n_staging
New Crowdin updates
2021-12-23 17:11:31 -07:00
daniel
8d697f8de9 New translations web.php (Occitan)
[ci skip]
2021-12-23 16:39:24 -07:00
daniel
b5c864a56f New translations web.php (French)
[ci skip]
2021-12-23 15:42:25 -07:00
daniel
5fa27d4ec9 New translations web.php (Basque)
[ci skip]
2021-12-23 14:40:55 -07:00
daniel
1c22c3b680 New translations web.php (Basque)
[ci skip]
2021-12-23 12:19:17 -07:00
daniel
a36b8cfccf New translations web.php (Persian)
[ci skip]
2021-12-23 11:03:33 -07:00
daniel
fbb1be99ce New translations web.php (Basque)
[ci skip]
2021-12-23 10:04:40 -07:00
daniel
132541cae8 New translations web.php (Basque)
[ci skip]
2021-12-23 08:59:47 -07:00
daniel
65fdd5f13c New translations web.php (Basque)
[ci skip]
2021-12-23 06:33:53 -07:00
daniel
da547344a6 New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-23 06:33:52 -07:00
daniel
ff2be7c49a New translations web.php (Basque)
[ci skip]
2021-12-23 04:19:18 -07:00
daniel
d8ed5739e8 New translations web.php (Turkish)
[ci skip]
2021-12-23 04:19:17 -07:00
daniel
c4c0f6daaf New translations web.php (Basque)
[ci skip]
2021-12-23 03:02:48 -07:00
daniel
c5ec38301c New translations web.php (Turkish)
[ci skip]
2021-12-23 03:02:47 -07:00
daniel
7fd5f5ef1c New translations web.php (Japanese)
[ci skip]
2021-12-23 03:02:46 -07:00
daniel
7b95d730f2 New translations web.php (Basque)
[ci skip]
2021-12-23 02:02:22 -07:00
daniel
0d142dc00b New translations web.php (Turkish)
[ci skip]
2021-12-23 02:02:21 -07:00
daniel
452dfecade New translations web.php (Japanese)
[ci skip]
2021-12-23 02:02:21 -07:00
daniel
466a65294d New translations web.php (German)
[ci skip]
2021-12-23 02:02:19 -07:00
daniel
ba56c8c187 New translations web.php (Japanese)
[ci skip]
2021-12-23 00:44:31 -07:00
daniel
4a0cb8aaac New translations web.php (Czech)
[ci skip]
2021-12-23 00:44:30 -07:00
daniel
be4b971ace New translations web.php (Czech)
[ci skip]
2021-12-22 23:46:45 -07:00
daniel
8e2a1e8240 New translations web.php (Arabic)
[ci skip]
2021-12-22 23:46:44 -07:00
daniel
1edb6fe224 New translations web.php (Greek)
[ci skip]
2021-12-22 22:48:23 -07:00
daniel
09c28046ef New translations web.php (Arabic)
[ci skip]
2021-12-22 22:48:22 -07:00
daniel
1a6ac406d6 New translations web.php (Arabic)
[ci skip]
2021-12-22 21:49:20 -07:00
daniel
6008a5d186 New translations web.php (Polish)
[ci skip]
2021-12-22 19:24:19 -07:00
daniel
c988f07895 New translations web.php (Dutch)
[ci skip]
2021-12-22 19:24:18 -07:00
daniel
a28d06a2ab New translations web.php (German)
[ci skip]
2021-12-22 19:24:16 -07:00
daniel
5945a4f4a0 New translations web.php (Spanish)
[ci skip]
2021-12-22 19:24:15 -07:00
daniel
3e3f56a934 New translations web.php (French)
[ci skip]
2021-12-22 19:24:14 -07:00
daniel
ca4206055c New translations web.php (Bengali)
[ci skip]
2021-12-22 18:08:40 -07:00
daniel
882d1b37f2 New translations web.php (Korean)
[ci skip]
2021-12-22 18:08:39 -07:00
daniel
6f11eb4885 New translations web.php (Arabic)
[ci skip]
2021-12-22 17:11:56 -07:00
daniel
f75273973b New translations web.php (Scottish Gaelic)
[ci skip]
2021-12-22 16:09:24 -07:00
daniel
95537611c9 New translations web.php (Basque)
[ci skip]
2021-12-22 16:09:23 -07:00
daniel
5685716446 New translations web.php (Indonesian)
[ci skip]
2021-12-22 16:09:23 -07:00
daniel
a0281168d6 New translations web.php (Portuguese)
[ci skip]
2021-12-22 16:09:22 -07:00
daniel
8c9fbdd547 New translations web.php (Dutch)
[ci skip]
2021-12-22 16:09:21 -07:00
daniel
97b04372c0 New translations web.php (German)
[ci skip]
2021-12-22 14:39:14 -07:00
daniel
2e557e6bba New translations web.php (Arabic)
[ci skip]
2021-12-22 14:39:13 -07:00
daniel
7857fd7091 New translations web.php (French)
[ci skip]
2021-12-22 14:39:12 -07:00
daniel
481dc1aaa6 New translations web.php (Scottish Gaelic)
[ci skip]
2021-12-22 13:23:07 -07:00
daniel
150070f8b1 New translations web.php (Basque)
[ci skip]
2021-12-22 13:23:06 -07:00
daniel
a4922de49e New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-22 13:23:05 -07:00
daniel
f4401184ce New translations web.php (Basque)
[ci skip]
2021-12-22 12:19:14 -07:00
daniel
3534d6c11a New translations web.php (Basque)
[ci skip]
2021-12-22 11:18:07 -07:00
daniel
1964408d66 New translations web.php (Arabic)
[ci skip]
2021-12-22 11:18:06 -07:00
daniel
1ecadfd9bd New translations web.php (French)
[ci skip]
2021-12-22 11:18:04 -07:00
daniel
d530e22878 New translations web.php (Welsh)
[ci skip]
2021-12-22 09:58:03 -07:00
daniel
b95d879b9e New translations web.php (Portuguese, Brazilian)
[ci skip]
2021-12-22 09:58:02 -07:00
daniel
36e9f47e69 New translations web.php (Russian)
[ci skip]
2021-12-22 09:58:01 -07:00
daniel
5d4ac0b161 New translations web.php (Arabic)
[ci skip]
2021-12-22 09:57:59 -07:00
daniel
1f21cc7543 New translations web.php (French)
[ci skip]
2021-12-22 09:57:58 -07:00
daniel
eb9914e8da New translations web.php (Welsh)
[ci skip]
2021-12-22 04:51:53 -07:00
daniel
a29c748961 New translations web.php (Russian)
[ci skip]
2021-12-22 04:51:52 -07:00
daniel
5be9671fd5 New translations web.php (Hebrew)
[ci skip]
2021-12-22 04:51:51 -07:00
daniel
434260d18f New translations web.php (German)
[ci skip]
2021-12-22 04:51:50 -07:00
daniel
215076ea6b New translations web.php (Spanish)
[ci skip]
2021-12-22 04:51:49 -07:00
daniel
29cde0eecc New translations web.php (French)
[ci skip]
2021-12-22 04:51:48 -07:00
daniel
bc5dc64d9d New translations web.php (Indonesian)
[ci skip]
2021-12-22 03:47:37 -07:00
daniel
7ece0b4764 New translations web.php (Galician)
[ci skip]
2021-12-22 03:47:36 -07:00
daniel
b70b57c625 New translations web.php (Polish)
[ci skip]
2021-12-22 03:47:35 -07:00
daniel
e4703a85fd New translations web.php (Hebrew)
[ci skip]
2021-12-22 03:47:34 -07:00
daniel
83580d0510 New translations web.php (German)
[ci skip]
2021-12-22 03:47:33 -07:00
daniel
82b97653d9 New translations web.php (Catalan)
[ci skip]
2021-12-22 03:47:32 -07:00
daniel
2c83df8a0f New translations web.php (Spanish)
[ci skip]
2021-12-22 03:47:31 -07:00
daniel
f72a3e2a78 New translations web.php (French)
[ci skip]
2021-12-22 03:47:30 -07:00
daniel
b3652a1e36 New translations web.php (Indonesian)
[ci skip]
2021-12-22 02:23:25 -07:00
daniel
73b7aa078b New translations web.php (Galician)
[ci skip]
2021-12-22 02:23:24 -07:00
daniel
004a647f9c New translations web.php (Italian)
[ci skip]
2021-12-22 02:23:23 -07:00
daniel
4cd77c6ca9 New translations web.php (German)
[ci skip]
2021-12-22 02:23:22 -07:00
daniel
9ee6cbd4b1 New translations web.php (Catalan)
[ci skip]
2021-12-22 02:23:21 -07:00
daniel
14a1679492 New translations web.php (French)
[ci skip]
2021-12-22 02:23:20 -07:00
daniel
4f2c8b39b3
Merge pull request #3071 from pixelfed/staging
Staging
2021-12-22 01:38:06 -07:00
Daniel Supernault
599ed20ec0
Update compiled assets 2021-12-22 01:35:49 -07:00
Daniel Supernault
08584b6dd3
Update i18n strings 2021-12-22 01:33:57 -07:00
daniel
781788bdcc
Merge pull request #3070 from pixelfed/l10n_staging
New Crowdin updates
2021-12-22 01:28:35 -07:00
daniel
4c7f4fd7cb New translations web.php (Indonesian)
[ci skip]
2021-12-22 01:27:48 -07:00
daniel
ce066baf34 New translations web.php (German)
[ci skip]
2021-12-22 01:27:42 -07:00
daniel
299283fe75 New translations web.php (Greek)
[ci skip]
2021-12-22 01:27:41 -07:00
daniel
bdd925551c New translations web.php (French)
[ci skip]
2021-12-22 01:27:37 -07:00
daniel
cf934ad994 New translations web.php (Polish)
[ci skip]
2021-12-22 01:27:34 -07:00
daniel
8719516be1 New translations web.php (Russian)
[ci skip]
2021-12-22 01:27:32 -07:00
daniel
4e8fb2f849
Merge pull request #3068 from pixelfed/l10n_staging
New Crowdin updates
2021-12-22 01:21:33 -07:00
daniel
bcba16f928
Merge pull request #3069 from pixelfed/staging
Staging
2021-12-22 01:00:50 -07:00
Daniel Supernault
4661dbadd1
Update compiled assets 2021-12-22 00:59:53 -07:00
Daniel Supernault
37dcc4ae13
Update PublicApiController 2021-12-22 00:59:03 -07:00
Daniel Supernault
7d3214d37e
Update web routes 2021-12-22 00:54:53 -07:00
Daniel Supernault
65064a8832
Update DiscoverComponent 2021-12-22 00:51:24 -07:00
Daniel Supernault
2befff8fd1
Update ComposeModal 2021-12-22 00:47:33 -07:00
daniel
75b8553ddc New translations web.php (Indonesian)
[ci skip]
2021-12-22 00:24:51 -07:00
daniel
25deee375f New translations web.php (Dutch)
[ci skip]
2021-12-22 00:24:50 -07:00
daniel
bded1737b5 New translations web.php (Hebrew)
[ci skip]
2021-12-22 00:24:49 -07:00
daniel
8f941d52bd New translations web.php (Greek)
[ci skip]
2021-12-22 00:24:47 -07:00
daniel
b2e221d2a3 New translations web.php (German)
[ci skip]
2021-12-22 00:24:46 -07:00
daniel
e6d029733c New translations web.php (Galician)
[ci skip]
2021-12-21 23:29:18 -07:00
daniel
7d57bb8ced New translations web.php (Russian)
[ci skip]
2021-12-21 23:29:17 -07:00
daniel
1d13ea98d9 New translations web.php (Dutch)
[ci skip]
2021-12-21 23:29:16 -07:00
daniel
4d124dbe41 New translations web.php (Hebrew)
[ci skip]
2021-12-21 23:29:15 -07:00
daniel
be706b273d
Merge pull request #3067 from pixelfed/staging
Staging
2021-12-21 22:50:01 -07:00
Daniel Supernault
fb742cf4db
Update changelog 2021-12-21 22:49:20 -07:00
Daniel Supernault
327073724f
Update StatusEntityLexer, prevent boosts and replies from being added to PublicTimelineService 2021-12-21 22:48:47 -07:00
daniel
657175366f
Merge pull request #3066 from pixelfed/staging
Staging
2021-12-21 22:38:02 -07:00
Daniel Supernault
5e1e207329
Update langs 2021-12-21 22:33:54 -07:00
Daniel Supernault
7bef159119
Add i18n dev command 2021-12-21 22:32:33 -07:00
daniel
adbc7dec0b
Merge pull request #3065 from pixelfed/l10n_staging
New Crowdin updates
2021-12-21 22:26:29 -07:00
daniel
91d85fd751 New translations web.php (German)
[ci skip]
2021-12-21 22:11:03 -07:00
daniel
3160f719b6
Merge pull request #3064 from pixelfed/staging
Staging
2021-12-21 21:57:34 -07:00
Daniel Supernault
5828a863cb
Update web routes 2021-12-21 21:54:48 -07:00
Daniel Supernault
67b37be2a7
Update compiled assets 2021-12-21 21:45:43 -07:00
Daniel Supernault
7e35ab80ad
Update web routes 2021-12-21 21:44:52 -07:00
Daniel Supernault
937cdfb7f9
Update StatusService 2021-12-21 21:40:10 -07:00
daniel
2a651b72d2
Merge pull request #3063 from pixelfed/staging
Staging
2021-12-21 21:33:34 -07:00
Daniel Supernault
e12227aee1
Update i18n 2021-12-21 21:26:52 -07:00
daniel
978d336c01
Merge pull request #3062 from pixelfed/l10n_staging
New Crowdin updates
2021-12-21 21:23:43 -07:00
daniel
fa0636d325 New translations web.php (Spanish)
[ci skip]
2021-12-21 21:12:07 -07:00
daniel
7c5da78cb0 New translations web.php (Afrikaans)
[ci skip]
2021-12-21 21:12:07 -07:00
daniel
a2d69257cf New translations web.php (Arabic)
[ci skip]
2021-12-21 21:12:05 -07:00
daniel
3bf2ddb1fc New translations web.php (Catalan)
[ci skip]
2021-12-21 21:12:04 -07:00
daniel
8ed2f843df New translations web.php (Czech)
[ci skip]
2021-12-21 21:12:03 -07:00
daniel
5a036b239f New translations web.php (Danish)
[ci skip]
2021-12-21 21:12:02 -07:00
daniel
b0056081f6 New translations web.php (German)
[ci skip]
2021-12-21 21:12:01 -07:00
daniel
9c0315cf5f New translations web.php (Greek)
[ci skip]
2021-12-21 21:12:00 -07:00
daniel
c085c9288a New translations web.php (Finnish)
[ci skip]
2021-12-21 21:11:59 -07:00
daniel
a543616502 New translations web.php (Hebrew)
[ci skip]
2021-12-21 21:11:58 -07:00
daniel
b5b08620e9 New translations web.php (Hungarian)
[ci skip]
2021-12-21 21:11:57 -07:00
daniel
3681811515 New translations web.php (Italian)
[ci skip]
2021-12-21 21:11:56 -07:00
daniel
dfa47b4673 New translations web.php (Japanese)
[ci skip]
2021-12-21 21:11:55 -07:00
daniel
2008fad133 New translations web.php (French)
[ci skip]
2021-12-21 21:11:54 -07:00
daniel
8b1f02b253 New translations web.php (Korean)
[ci skip]
2021-12-21 21:11:53 -07:00
daniel
1d81cc5431 New translations web.php (Norwegian)
[ci skip]
2021-12-21 21:11:52 -07:00
daniel
01466f5355 New translations web.php (Polish)
[ci skip]
2021-12-21 21:11:51 -07:00
daniel
c6ab146a0f New translations web.php (Portuguese)
[ci skip]
2021-12-21 21:11:50 -07:00
daniel
0ee0b13da8 New translations web.php (Russian)
[ci skip]
2021-12-21 21:11:49 -07:00
daniel
b388b9fde7 New translations web.php (Serbian (Cyrillic))
[ci skip]
2021-12-21 21:11:48 -07:00
daniel
82e804feb5 New translations web.php (Swedish)
[ci skip]
2021-12-21 21:11:47 -07:00
daniel
1d6cc9cfcc New translations web.php (Turkish)
[ci skip]
2021-12-21 21:11:46 -07:00
daniel
2c813e32f5 New translations web.php (Ukrainian)
[ci skip]
2021-12-21 21:11:45 -07:00
daniel
ad44111bb0 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-21 21:11:44 -07:00
daniel
193d517c81 New translations web.php (Vietnamese)
[ci skip]
2021-12-21 21:11:42 -07:00
daniel
ff9aaadd9e New translations web.php (Dutch)
[ci skip]
2021-12-21 21:11:41 -07:00
daniel
8be8b93fbb New translations web.php (Romanian)
[ci skip]
2021-12-21 21:11:41 -07:00
Daniel Supernault
b1a6ce029c
Add lang strings 2021-12-21 20:54:33 -07:00
daniel
37212de5cd
Merge pull request #3061 from pixelfed/l10n_staging
New Crowdin updates
2021-12-21 20:54:14 -07:00
daniel
af3e204e6e New translations web.php (German)
[ci skip]
2021-12-21 20:12:53 -07:00
daniel
76c61cab3a
Merge pull request #3060 from pixelfed/l10n_staging
New Crowdin updates
2021-12-21 18:02:56 -07:00
daniel
85db47200f New translations web.php (Afrikaans)
[ci skip]
2021-12-21 18:01:03 -07:00
daniel
2c0545c552 New translations web.php (German)
[ci skip]
2021-12-21 18:00:59 -07:00
daniel
9630c34eb1 New translations web.php (Hungarian)
[ci skip]
2021-12-21 18:00:57 -07:00
daniel
c1bf2de10c New translations web.php (Korean)
[ci skip]
2021-12-21 18:00:55 -07:00
daniel
763d3bb4a6 New translations web.php (Norwegian)
[ci skip]
2021-12-21 18:00:54 -07:00
daniel
6f6a167d8c New translations web.php (Serbian (Cyrillic))
[ci skip]
2021-12-21 18:00:51 -07:00
daniel
c8b488b24a New translations web.php (Ukrainian)
[ci skip]
2021-12-21 18:00:50 -07:00
daniel
967e495363 New translations web.php (Chinese Traditional)
[ci skip]
2021-12-21 18:00:49 -07:00
daniel
e95909b093 New translations web.php (Vietnamese)
[ci skip]
2021-12-21 18:00:48 -07:00
daniel
d01a32b35f New translations web.php (Romanian)
[ci skip]
2021-12-21 18:00:46 -07:00
Daniel Supernault
edcf9e2b4f
Update crowdin config 2021-12-21 18:00:05 -07:00
Daniel Supernault
b1dde37adc
Update l10n 2021-12-21 17:49:44 -07:00
Daniel Supernault
132c8b8b71
Add web lang files 2021-12-21 17:43:37 -07:00
daniel
acd607a867 Update Crowdin configuration file 2021-12-21 17:23:30 -07:00
daniel
ed3a2f90f2
Merge pull request #3057 from pixelfed/staging
Staging
2021-12-21 17:20:05 -07:00
Daniel Supernault
7ff120c943
Add crowdin configuration 2021-12-21 17:18:24 -07:00
Daniel Supernault
f7d9b40b03
Add frontend translation strings 2021-12-21 17:16:47 -07:00
daniel
5442e232dc
Merge pull request #3056 from pixelfed/staging
Update ComposeController, refactor compose_settings
2021-12-19 04:25:49 -07:00
Daniel Supernault
a114c7d12a
Update changelog 2021-12-19 04:20:53 -07:00
Daniel Supernault
edc2958bf2
Update ComposeController, refactor compose_settings 2021-12-19 04:19:26 -07:00
daniel
ff1a12cdd7
Merge pull request #3055 from pixelfed/staging
Improve DiscoverService
2021-12-19 04:08:15 -07:00
Daniel Supernault
aee9b994e3
Improve DiscoverService 2021-12-19 04:07:40 -07:00
daniel
8d002415e7
Merge pull request #3054 from pixelfed/staging
Update console kernel, add db session garbage collector that runs twi…
2021-12-19 01:29:20 -07:00
Daniel Supernault
fd2d37432b
Update changelog 2021-12-19 01:28:55 -07:00
Daniel Supernault
03b0a62a22
Update console kernel, add db session garbage collector that runs twice daily 2021-12-19 01:25:49 -07:00
daniel
935fa47c59
Merge pull request #3053 from pixelfed/staging
Staging
2021-12-19 01:03:43 -07:00
Daniel Supernault
0aad8d5934
Fix typo in api route 2021-12-19 00:51:57 -07:00
Daniel Supernault
367d74fbfb
Update ApiV1Controller, use DiscoverService for discoverPosts method 2021-12-19 00:48:59 -07:00
Daniel Supernault
f36da7816f
Update InternalApiController, use DiscoverService for discoverPosts method 2021-12-19 00:48:05 -07:00
Daniel Supernault
493c5ca0ce
Add DiscoverService 2021-12-19 00:43:09 -07:00
daniel
23e82e28b4
Merge pull request #3052 from pixelfed/staging
Staging
2021-12-18 19:38:23 -07:00
Daniel Supernault
2f0cd86ce3
Add webfonts 2021-12-18 19:37:53 -07:00
daniel
f5387e84e8
Merge pull request #3047 from Wv5twkFEKh54vo4tta9yu7dHa3/patch-2
Add discover to routes
2021-12-18 19:35:07 -07:00
daniel
d33531568f
Merge pull request #2992 from xplosionmind/patch-1
Added “Run on YunoHost”
2021-12-18 19:33:52 -07:00
daniel
c8a8928da4
Merge pull request #3030 from lisbonjoker/patch-1
Patch 1 - Portuguese Translation
2021-12-18 19:31:54 -07:00
daniel
a65c6e2183
Merge pull request #3029 from Strubbl/docker-apache-update-debian-and-composer
docker-apache: update Debian to Bullseye and composer to 2.1.14
2021-12-18 19:30:44 -07:00
daniel
f3814c1987
Merge pull request #3048 from pixelfed/staging
Staging
2021-12-17 00:28:18 -07:00
daniel
7860499e67
Merge pull request #3009 from pixelfed/pwa-manifest
Update manifest with new icons, shortcuts, and categories
2021-12-17 00:27:49 -07:00
Wv5twkFEKh54vo4tta9yu7dHa3
82d1ca14bb Add discover to routes 2021-12-15 12:31:54 +01:00
daniel
c580c3dc5e
Merge pull request #3042 from pixelfed/staging
Update compiled assets
2021-12-15 00:08:04 -07:00
Daniel Supernault
0b27e988e6
Update compiled assets 2021-12-15 00:07:36 -07:00
daniel
9a57002b13
Merge pull request #3041 from pixelfed/staging
Drop PHP 7.3 support, make 7.4 the minimum and fix 8.0 support
2021-12-14 23:50:52 -07:00
Daniel Supernault
bf4bd00bbc
Update composer.lock 2021-12-14 23:49:19 -07:00
Daniel Supernault
9ad91a65d6
Update changelog 2021-12-14 23:47:28 -07:00
Daniel Supernault
33b69499dd
Update circleci config 2021-12-14 23:27:09 -07:00
Daniel Supernault
57268a827e
Update circleci config 2021-12-14 23:07:17 -07:00
Daniel Supernault
9aa883d621
Drop PHP 7.3 support, make PHP 7.4 min version 2021-12-14 23:06:59 -07:00
Daniel Supernault
b1ce49191b
Fix PHP 8 support 2021-12-14 22:59:31 -07:00
daniel
05041b95f3
Merge pull request #3040 from pixelfed/staging
Staging
2021-12-14 22:37:55 -07:00
Daniel Supernault
a065e17cc2
Update pixelfed config, remove env_editor 2021-12-14 22:37:07 -07:00
Daniel Supernault
f411c4e004
Update changelog 2021-12-14 22:36:18 -07:00
Daniel Supernault
dc3168506c
Update compiled assets 2021-12-14 22:34:14 -07:00
Daniel Supernault
91e078e031
Update Activity component 2021-12-14 22:26:10 -07:00
Daniel Supernault
e975372631
Update ComposeModal 2021-12-14 22:25:50 -07:00
Daniel Supernault
b78a51890d
Update Timeline component, change api endpoint 2021-12-14 22:24:15 -07:00
Daniel Supernault
0cc1365f98
Update PhotoPresenter component, add lightbox toggle 2021-12-14 22:18:56 -07:00
Daniel Supernault
0c8fffbd73
Update PollCard component, add showBorder prop 2021-12-14 22:18:01 -07:00
Daniel Supernault
01ca1edd84
Update ContextMenu component, fix account url paths 2021-12-14 22:17:08 -07:00
Daniel Supernault
e09a14d892
Update NotificationCard, update api endpoint and add group notifications 2021-12-14 22:16:13 -07:00
Daniel Supernault
fefbc44a5f
Update Hashtag component, fix spinner 2021-12-14 22:13:59 -07:00
Daniel Supernault
c5145b3db1
Remove ace 2021-12-14 21:49:48 -07:00
Daniel Supernault
f3f35f590d
Remove admin .env editor 2021-12-14 21:46:24 -07:00
Daniel Supernault
9093549dc2
Remove quill editor 2021-12-14 21:37:19 -07:00
daniel
6b055724a5
Merge pull request #3039 from pixelfed/staging
Staging
2021-12-14 21:20:29 -07:00
Daniel Supernault
642b802022
Update Http Kernel 2021-12-14 21:18:29 -07:00
Daniel Supernault
b8ebc00212
Update SharePipeline, fix variable bug 2021-12-14 21:13:30 -07:00
daniel
bb243222e2
Merge pull request #3038 from pixelfed/staging
Update CommentPipeline, fix variable bug
2021-12-14 21:09:54 -07:00
Daniel Supernault
0616a859d5
Update CommentPipeline, fix variable bug 2021-12-14 21:09:23 -07:00
daniel
c19cd49523
Merge pull request #3037 from pixelfed/staging
Add Cloud Backups
2021-12-14 20:32:22 -07:00
Daniel Supernault
7ca63f2eb6
Update changelog 2021-12-14 20:27:17 -07:00
Daniel Supernault
3515a98e55
Add Cloud Backups, a command to store backups on S3 or compatible filesystems 2021-12-14 20:08:44 -07:00
daniel
3d848fd5b0
Merge pull request #3035 from pixelfed/staging
Staging
2021-12-14 20:04:13 -07:00
Daniel Supernault
ce8edb0998
Update changelog 2021-12-14 19:58:17 -07:00
Daniel Supernault
83b48b5681
Update StatusService, improve cache invalidation 2021-12-12 22:30:55 -07:00
Daniel Supernault
c2910e5d42
Update MediaService, return empty array if cantt find status 2021-12-12 19:33:20 -07:00
Daniel Supernault
44b32d8bb6
Update SharePipeline 2021-12-12 19:32:11 -07:00
Daniel Supernault
466286af92
Update ApiV1Controller, improve statusesById perf and dispatch CommentPipeline job when applicable 2021-12-10 22:03:33 -07:00
Daniel Supernault
b6b0837f49
Update CommentPipeline, move reply_count calculation to comment pipeline job and improve count calculation 2021-12-10 21:55:42 -07:00
Artur Mancha
7a448da89b
Merge branch 'pixelfed:dev' into patch-1 2021-12-05 17:44:46 +00:00
Daniel Supernault
945a7e49f5
Update InternalApiController, prevent moderation actions against admin accounts 2021-12-05 01:42:55 -07:00
Daniel Supernault
2923453e13
Update PublicApiController 2021-12-04 20:41:46 -07:00
Daniel Supernault
fe64e18712
Update LikePipeline jobs, fix likes_count calculation 2021-12-04 20:31:36 -07:00
Daniel Supernault
a641d3a32c
Update changelog 2021-12-04 17:55:56 -07:00
Daniel Supernault
079804e65b
Update ApiV1Controller, improve settings and add discoverPosts endpoint 2021-12-04 17:55:38 -07:00
Daniel Supernault
cdaa153003
Update StoryController 2021-12-04 17:46:23 -07:00
Daniel Supernault
19d140b020
Update FollowerService 2021-12-04 17:37:44 -07:00
Daniel Supernault
0342027867
Update UserSetting model, cast compose_settings and other as json 2021-12-04 17:35:50 -07:00
Daniel Supernault
98f76abbe8
Update AccountTransformer, add note_text and location fields 2021-12-04 17:34:32 -07:00
Daniel Supernault
f88b3a2fb9
Update StatusService, add getDirectMessage method 2021-12-04 17:33:32 -07:00
Daniel Supernault
fbaed93eda
Update SearchApiV2Service, improve performance and include hashtag post counts when applicable 2021-12-04 17:32:43 -07:00
Daniel Supernault
a37971dd28
Add HashtagService 2021-12-04 17:31:53 -07:00
Daniel Supernault
9d9e9ce7fa
Update MediaStorageService, improve header parsing 2021-12-04 17:30:05 -07:00
Daniel Supernault
2aa73c1ffa
Update AccountService, add dynamic user settings methods 2021-12-04 17:27:58 -07:00
daniel
cd1a263f6e
Merge pull request #3034 from pixelfed/staging
Staging
2021-12-04 15:31:40 -07:00
Daniel Supernault
c4d3851afe
Update changelog 2021-12-04 15:31:09 -07:00
Daniel Supernault
b8e9056ee3
Update UserInviteController, fixes #3017 2021-12-04 15:30:08 -07:00
daniel
59f6a2ead7
Merge pull request #3032 from pixelfed/staging
Staging
2021-12-03 20:49:28 -07:00
Daniel Supernault
78b2650f3a
Update changelog 2021-12-03 20:48:45 -07:00
Daniel Supernault
e5387d6742
Add StatusMentionService, fixes #3026 2021-12-03 20:47:00 -07:00
daniel
89d62176cb
Merge pull request #3031 from samuelroland/remote-redirect-remove
Remove remoteRedirect() for profile website fix #2996
2021-12-03 18:24:59 -07:00
Samuel Roland
3880ab2230
Remove remoteRedirect() for profile website 2021-12-04 02:15:29 +01:00
Artur Mancha
f7f62b3df8
Create helpcenter.php
Added missing translation
2021-12-02 11:14:52 +01:00
Artur Mancha
821eb77f41
Create exception.php
Added missing translation
2021-12-02 11:11:48 +01:00
Artur Mancha
9d92608d2c
Update auth.php
Minor change
2021-12-02 11:07:47 +01:00
Artur Mancha
3904849e36
Update validation.php
Minor change
2021-12-02 11:07:26 +01:00
Artur Mancha
9fa1f5b634
Update timeline.php
Improved translation
2021-12-02 11:04:38 +01:00
Artur Mancha
5143a4b877
Update site.php
Improved translation
2021-12-02 11:04:11 +01:00
Artur Mancha
acc26b34a7
Update profile.php
Improved translation
2021-12-02 11:02:42 +01:00
Artur Mancha
6d3618d2cf
Update passwords.php
Improved translation
2021-12-02 10:57:58 +01:00
Artur Mancha
7c4ac2dbcc
Update notification.php
Improved translation
2021-12-02 10:54:01 +01:00
Artur Mancha
93392b85b2
Update navmenu.php
Pequena adição
2021-12-02 10:51:35 +01:00
Artur Mancha
d2518994b9
Update auth.php
Improved translation
2021-12-02 10:50:09 +01:00
Sven Fischer
b57e397d5e docker-apache: update Debian to Bullseye and composer to 2.1.14 2021-11-30 20:17:45 +01:00
a
ed2d3b1388 Update manifest with new icons, shortcuts, and categories 2021-11-16 07:18:56 -06:00
daniel
0c3691efc7
Merge pull request #3008 from pixelfed/staging
Update npm deps
2021-11-15 19:54:09 -07:00
Daniel Supernault
8dee519904
Update npm deps 2021-11-15 19:53:37 -07:00
daniel
36b7a63d14
Merge pull request #3006 from pixelfed/staging
Staging
2021-11-15 19:50:01 -07:00
Daniel Supernault
f4af875a1c
Update composer deps 2021-11-15 19:47:47 -07:00
Daniel Supernault
ed62a09f9a
Update lexer utils 2021-11-15 19:35:10 -07:00
Daniel Supernault
639e9859ed
Add AutolinkService 2021-11-15 19:33:52 -07:00
daniel
ec4482b02d
Merge pull request #3005 from pixelfed/staging
Staging
2021-11-10 22:39:35 -07:00
Daniel Supernault
d02a8f005f
Update login view 2021-11-10 22:12:46 -07:00
Daniel Supernault
9811dda7a7
Update login view 2021-11-10 22:09:06 -07:00
daniel
8a0ba77867
Merge pull request #3001 from scottaohara/patch-1
fix broken accessibility for search field
2021-11-10 22:05:21 -07:00
Daniel Supernault
4cb956aea2
Update AdminStatsService, add storage() method 2021-11-10 21:54:50 -07:00
daniel
59d76bb9b3
Merge pull request #2956 from h3xx/fix-psql-grouping
Fix grouping error in PostgreSQL
2021-11-10 21:53:30 -07:00
daniel
ceba6b90a1
Merge pull request #3004 from pixelfed/staging
Update Autospam service
2021-11-10 21:51:20 -07:00
Daniel Supernault
98bfa420c6
Update changelog 2021-11-10 21:46:59 -07:00
Daniel Supernault
ae8c751796
Update Autospam service, add mark all as read and mark all as not spam options and filter active, spam and not spamreports 2021-11-10 21:46:31 -07:00
Daniel Supernault
dff3dad1c8
Update AdminController, move report methods to AdminReports trait 2021-11-09 00:06:56 -07:00
daniel
d4d92187db
Merge pull request #3002 from pixelfed/staging
Staging
2021-11-08 23:26:52 -07:00
Daniel Supernault
a956b50e8e
Add account.email blade view 2021-11-08 23:22:55 -07:00
Daniel Supernault
e6d9437846
Update middleware 2021-11-08 23:21:53 -07:00
Daniel Supernault
0e892bb3fd
Update AccountController 2021-11-08 23:13:10 -07:00
Daniel Supernault
82a84a733b
Update changelog 2021-11-08 23:03:35 -07:00
Daniel Supernault
bc65938757
Add manual email verification requests 2021-11-08 23:02:34 -07:00
Scott O'Hara
345d7eed2c
fix broken accessibility for search field
This PR fixes the incorrect use of `role=search` on the `<input>` element.  The `search` role is not a type of text input, and its use here was negating the semantics of the text field's ability to announce itself as a text field to screen readers.  The proper place for a `role=search` is on the `<form>` element that contains this input, so I have placed it there in this fix.

There are likely other accessibility improvements that could be made in this file, but this was an easy fix that I figured I could make a quick PR for.  

Thanks
2021-11-06 10:25:46 -04:00
Daniel Supernault
9bd53524c7
Update setting layouts 2021-11-05 21:51:52 -06:00
Daniel Supernault
3d814de9a6
Update email settings view 2021-11-05 18:37:33 -06:00
daniel
7899242ee8
Merge pull request #2999 from pixelfed/staging
Update api routes
2021-11-03 23:47:28 -06:00
Daniel Supernault
cbd755082e
Update api routes 2021-11-03 23:47:00 -06:00
daniel
71706d1e45
Merge pull request #2998 from pixelfed/staging
Staging
2021-11-03 23:40:24 -06:00
Daniel Supernault
16f2758808
Update changelog 2021-11-03 23:37:59 -06:00
Daniel Supernault
2ae527c0f3
Update Status model, use AccountService to generate urls instead of loading profile relation 2021-11-03 23:29:12 -06:00
daniel
64112e41df
Merge pull request #2997 from pixelfed/staging
Staging
2021-11-03 19:41:37 -06:00
Daniel Supernault
694ce6ff3d
Update changelog 2021-11-03 19:41:12 -06:00
Daniel Supernault
aa2dd26c1b
Update PublicApiController, fix private account statuses api. Closes #2995 2021-11-03 19:40:46 -06:00
Tommi
74d2401cf5
Added “Run on YunoHost”
Pixelfed is super simple to self-host even for people with limited technical skills, thanks to YunoHost. Such a great advantage (which very few Fediverse social media have) should be made more visible, as in the [Wallabag repository](https://github.com/wallabag/wallabag#run-on-yunohost '“Run on YunoHost“ on Wallabag README on GitHub')
2021-10-31 00:04:43 +02:00
Daniel Supernault
0107e8fd68
Update Localization util, filter out .DS_Store 2021-10-30 00:56:48 -06:00
daniel
3d2ac8bce9
Merge pull request #2991 from pixelfed/staging
Staging
2021-10-30 00:13:30 -06:00
Daniel Supernault
4dda3b3763
Update changelog 2021-10-30 00:12:08 -06:00
Daniel Supernault
7df3540b08
Update verify_credentials api endpoint to improve performance 2021-10-30 00:11:35 -06:00
daniel
2a91b5321e
Merge pull request #2984 from pixelfed/staging
Staging
2021-10-22 19:03:07 -06:00
Daniel Supernault
a982d1900f
Update changelog 2021-10-22 19:01:45 -06:00
Daniel Supernault
2b254c8f94
Update compiled assets 2021-10-22 19:01:22 -06:00
Daniel Supernault
3169f68e65
Update dark mode styles, fix black box on stories. Closes #2982 2021-10-22 18:56:29 -06:00
daniel
14ee32adf0
Merge pull request #2983 from pixelfed/staging
Staging
2021-10-21 19:07:31 -06:00
Daniel Supernault
1e44a8128a
Update nav, fix link 2021-10-21 19:04:46 -06:00
Daniel Supernault
304cab98a7
Update changelog 2021-10-21 19:03:07 -06:00
Daniel Supernault
192553ff77
Update public timeline api, add experimental cache 2021-10-21 19:02:15 -06:00
Dan Church
b167873632
Fix grouping error in PostgreSQL 2021-10-20 14:11:33 -05:00
Daniel Supernault
37abcf3898
Update public timeline api, use cached sorted set and client side block/mute filtering 2021-10-20 04:31:07 -06:00
Daniel Supernault
be194b8a3f
Update Timeline component, apply block/mute filters client side for local and network timelines 2021-10-20 04:22:19 -06:00
Daniel Supernault
5167c68d6b
Update Activity component, fix missing types 2021-10-20 04:19:20 -06:00
Daniel Supernault
02237845b5
Update Profile component, improve error messages when block/mute limit reached 2021-10-20 04:17:46 -06:00
Daniel Supernault
bc16b936d6
Update StatusCard component 2021-10-20 04:16:39 -06:00
daniel
708a363c60
Merge pull request #2981 from pixelfed/staging
Staging
2021-10-19 23:40:27 -06:00
Daniel Supernault
4225f32af0
Update changelog 2021-10-19 23:38:34 -06:00
Daniel Supernault
eac6b76779
Update horizon config 2021-10-19 23:15:03 -06:00
Daniel Supernault
f0d4c17236
Update Autospam service, use silent classification for better user experience 2021-10-19 23:13:12 -06:00
Daniel Supernault
64c75bdaec
Update story views 2021-10-19 22:07:57 -06:00
daniel
2ecba8e144
Merge pull request #2979 from pixelfed/staging
Staging
2021-10-19 21:03:16 -06:00
daniel
1badbf83cd
Merge pull request #2973 from okpierre/patch-11
Improve tappable area
2021-10-19 20:58:46 -06:00
daniel
1d57085ba8
Merge pull request #2971 from HDVinnie/patch-1
Create SECURITY.md
2021-10-19 20:57:39 -06:00
daniel
ce489fac41
Merge pull request #2969 from nogafam-es/staging
Fix PWA not working on firefox because of wrong icon sizes
2021-10-19 20:57:09 -06:00
daniel
a53692f6bf
Merge pull request #2957 from h3xx/fix-memory-limit
Fix global memory limit set in constructor
2021-10-19 20:49:15 -06:00
daniel
78aee2d23c
Merge pull request #2948 from criadoperez/docker-compose-patch
docker-compose file fix
2021-10-19 20:46:52 -06:00
daniel
3d2c98c033
Merge pull request #2941 from rfonseca/rfonseca-patch-1
Fix starting check of username to allow numbers.
2021-10-19 20:45:55 -06:00
daniel
2aab326bc4
Merge pull request #2934 from vszakats/patch-1
fix 'powered by pixelfed' link to be HTTPS
2021-10-19 20:44:20 -06:00
daniel
2617ba93ed
Merge pull request #2928 from brabitom/dev
Improve Czech localization
2021-10-19 20:42:21 -06:00
daniel
f79486c448
Merge branch 'staging' into dev 2021-10-19 20:41:49 -06:00
daniel
853d62e83f
Merge pull request #2978 from pixelfed/staging
Staging
2021-10-19 20:30:30 -06:00
Daniel Supernault
bb1e3267a4
Update composer 2021-10-19 20:23:33 -06:00
Daniel Supernault
b8a49d457b
Update composer deps 2021-10-19 20:06:30 -06:00
Daniel Supernault
66f04bec25
Update InstanceService 2021-10-19 19:51:14 -06:00
Daniel Supernault
7a9a06e0fd
Update NotificationTransformer 2021-10-19 19:49:44 -06:00
Daniel Supernault
601992ed48
Update changelog 2021-10-19 19:47:23 -06:00
Daniel Supernault
ba5a01f8b8
Update compiled assets 2021-10-19 19:47:12 -06:00
Daniel Supernault
dbc3c0fbe3
Update timeago helper, add long form option 2021-10-19 19:44:41 -06:00
Daniel Supernault
7886fd5998
Update Activity component, only show context button for actionable activities 2021-10-19 19:43:40 -06:00
Daniel Supernault
f4bd5672b1
Update Timeline component, cascade relationship state change 2021-10-19 19:41:51 -06:00
Daniel Supernault
0436b124e4
Update StatusCard component, add relationship state button 2021-10-19 19:40:31 -06:00
Daniel Supernault
2609c86ad6
Update auth config, add throttle limit to password resets 2021-10-14 20:37:46 -06:00
okpierre
d500626295
Update index.blade.php 2021-10-14 19:14:46 -04:00
HDVinnie
530bac2ffa
Create SECURITY.md 2021-10-13 01:07:01 -04:00
nogafam.es Admin
2d493f5d5f Fix PWA not working on firefox because of wrong icon sizes 2021-10-10 15:56:22 +02:00
Daniel Supernault
e5454620e6
Update HomeTimeline api 2021-10-07 04:49:33 -06:00
Daniel Supernault
bef959f451
Update PublicApiController, use AccountService in accountStatuses method 2021-10-07 03:30:23 -06:00
Daniel Supernault
80d9b9399a
Refactor following & relationship logic. Replace FollowerObserver with FollowerService and added RelationshipService to cache results. Removed NotificationTransformer includes and replaced with cached services to improve performance and reduce database queries. 2021-10-07 03:27:13 -06:00
Daniel Supernault
0a8eb81bf0
Update InstanceActorController, improve json seralization by not escaping slashes 2021-10-07 00:46:20 -06:00
Daniel Supernault
4505d1f0f9
Update FederationController, move well-known to api middleware and cache webfinger lookups 2021-10-07 00:34:37 -06:00
Dan Church
c685e3e9f4
Fix global memory limit set in constructor
Instead, set it when running.

Having the set in the constructor was causing memory_limit to be changed
for "artisan queue:work" processes.
2021-09-27 15:09:00 -05:00
Daniel Supernault
754151dc84
Update RemotePost, RemoteProfile components, add fallback avatars 2021-09-22 20:17:34 -06:00
Daniel Supernault
0299aa5b63
Update video presenters, add playsinline attribute to video tags 2021-09-22 20:12:28 -06:00
Daniel Supernault
7b3e672d89
Update NoteTransformer, fix tag array 2021-09-22 20:04:23 -06:00
criadoperez
bbccfdeff2
docker-compose file fix 2021-09-21 17:08:19 +02:00
daniel
dbf314151e
Merge pull request #2946 from pixelfed/staging
Staging
2021-09-20 00:23:19 -06:00
daniel
8c6ef9bae2
Merge branch 'dev' into staging 2021-09-20 00:23:10 -06:00
Daniel Supernault
9b4b78ef31
Update changelog 2021-09-20 00:22:09 -06:00
Daniel Supernault
a900de2121
Update HttpSignatures, update instance actor headers. Fixes #2935 2021-09-20 00:21:16 -06:00
daniel
68437e614d
Staging (#2927)
* Created localized exception.php (DE)

* Update German translation

* Update German translation

* Update de localization, Create New Post -> New

* Formatting

* Update site.php (#2889)

Co-authored-by: daniel <danielsupernault@gmail.com>

* Formatting

* Update controllers, fixes #2906

* Update NotificationService, fix 500 bug

* Update changelog

Co-authored-by: forenta <ueblesurmeli-github@yahoo.de>
Co-authored-by: Tomas Brabenec <tomas@brabenec.net>
2021-09-19 23:10:18 -06:00
daniel
213bed7cd1
Merge branch 'dev' into staging 2021-09-19 23:10:00 -06:00
Daniel Supernault
3c7ac155f5
Update changelog 2021-09-19 23:07:01 -06:00
Daniel Supernault
4a609dc377
Update NotificationService, fix 500 bug 2021-09-19 23:05:56 -06:00
Rodrigo Fonseca
31330e0aed
Fix starting check of username to allow numbers.
The check for the first letter of username used to be !ctype_alpha, but the error message says "Must start with a letter or number." Updated check to be !ctype_alnum, to be coherent with the error message.
2021-09-16 22:36:30 -07:00
Viktor Szakats
275e711624
fix 'powered by pixelfed' link to be HTTPS 2021-09-15 05:00:22 +02:00
brabitom
09edaa87b7 Improve Czech localization 2021-09-11 10:17:40 +02:00
Daniel Supernault
ac6b3f07de
Update controllers, fixes #2906 2021-09-10 21:44:29 -06:00
Daniel Supernault
a1d7586be4
Formatting 2021-09-10 21:29:45 -06:00
Tomas Brabenec
9f0f5a3d46
Update site.php (#2889)
Co-authored-by: daniel <danielsupernault@gmail.com>
2021-09-10 21:28:59 -06:00
Daniel Supernault
65655eaa89
Formatting 2021-09-10 21:25:30 -06:00
daniel
42816bd329
Merge pull request #2916 from forenta/patch-3
Update German translation
2021-09-10 21:25:02 -06:00
Daniel Supernault
44dfeb8c9a
Update de localization, Create New Post -> New 2021-09-10 21:23:47 -06:00
daniel
121e8f99a7
Merge pull request #2915 from forenta/patch-2
Update German translation
2021-09-10 21:22:46 -06:00
daniel
df972ea157
Merge pull request #2914 from forenta/patch-1
Created localized exception.php (DE)
2021-09-10 21:20:22 -06:00
daniel
cdfb6ac25f
Merge pull request #2926 from pixelfed/staging
Staging
2021-09-10 21:08:20 -06:00
Daniel Supernault
0fc3a1d6a5
Formatting 2021-09-10 21:08:04 -06:00
daniel
14f9033715
Merge pull request #2868 from nogafam-es/staging
Fix PWA implementation to support Gecko-based browsers, using html link tag
2021-09-10 21:02:36 -06:00
daniel
931cafc9bc
Merge pull request #2858 from shynome/i18n_zh_cn
add zh-cn lang
2021-09-10 20:58:54 -06:00
daniel
907945a7a0
Merge pull request #2923 from Wv5twkFEKh54vo4tta9yu7dHa3/patch-1
Fix media upload
2021-09-10 20:55:55 -06:00
Wv5twkFEKh54vo4tta9yu7dHa3
13f65629e1 Fix error 500 on image upload
Without this missing import, the server threw "Class 'App\Http\Controllers\Api\UserSetting' not found" when trying to upload images.
2021-09-09 15:03:00 +02:00
daniel
1f1c5da1f5
Merge pull request #2919 from pixelfed/staging
v0.11.1
2021-09-07 21:21:55 -06:00
Daniel Supernault
18f3e08851
Update changelog 2021-09-07 21:21:20 -06:00
Daniel Supernault
fe42c0b245
Bump version 2021-09-07 21:17:39 -06:00
Daniel Supernault
d4df221afa
Update changelog 2021-09-07 21:17:07 -06:00
Daniel Supernault
48eb75baab
Update compiled assets 2021-09-07 21:16:17 -06:00
Daniel Supernault
0584f9ee95
Update ApiV1Controller, fix empty public timeline bug 2021-09-07 21:07:20 -06:00
Daniel Supernault
00c32360ae
Update StoryController, fix postgres bug. Fixes #2904 2021-09-06 01:41:24 -06:00
Daniel Supernault
32995064ec
Update Profile model 2021-09-06 00:29:06 -06:00
forenta
85847f7a9f
Update German translation 2021-09-05 21:23:24 +02:00
forenta
f0bcab4b41
Update German translation 2021-09-05 21:21:49 +02:00
forenta
236559a4d9
Created localized exception.php (DE) 2021-09-05 21:19:35 +02:00
daniel
687030090f
Merge pull request #2909 from pixelfed/staging
Update AccountService, fix status bug
2021-09-04 21:13:10 -06:00
Daniel Supernault
9e630851a3
Update AccountService, fix status bug 2021-09-04 21:12:44 -06:00
daniel
82c5e74feb
Merge pull request #2907 from pixelfed/staging
Update StoryService, fix division by zero bug
2021-09-04 18:07:12 -06:00
Daniel Supernault
c3ffd5cbd6
Update changelog 2021-09-04 18:05:43 -06:00
Daniel Supernault
6ae1ba0a64
Update StoryService, fix division by zero bug 2021-09-04 17:55:58 -06:00
daniel
35de09dcf3
Merge pull request #2902 from pixelfed/staging
Staging
2021-09-04 03:23:11 -06:00
Daniel Supernault
97ca139c76
Update changelog 2021-09-04 03:22:33 -06:00
Daniel Supernault
0f00be4d98
Update DirectMessageController, fix autocomplete bug 2021-09-04 03:22:05 -06:00
daniel
945568da6a
Merge pull request #2901 from pixelfed/staging
Update Profile, fix following count bug
2021-09-04 03:19:06 -06:00
Daniel Supernault
29d7514a55
Update changelog 2021-09-04 03:18:19 -06:00
Daniel Supernault
ee9f079551
Update Profile, fix following count bug 2021-09-04 03:17:54 -06:00
daniel
78a277d125
Merge pull request #2900 from pixelfed/staging
Staging
2021-09-04 02:00:02 -06:00
Daniel Supernault
d32d05eed0
Update story gc 2021-09-04 01:59:50 -06:00
Daniel Supernault
84382c8aa9
Update changelog 2021-09-04 01:56:08 -06:00
Daniel Supernault
7dee8f58fe
Update StoryComposeController, fix expiry bug 2021-09-04 01:55:29 -06:00
daniel
4ec357bfd0
Merge pull request #2899 from pixelfed/staging
Add remote story view
2021-09-04 00:15:37 -06:00
Daniel Supernault
8c86953f42
Add remote story view 2021-09-04 00:15:06 -06:00
daniel
81073f4269
Merge pull request #2898 from pixelfed/staging
Update AP Inbox
2021-09-04 00:12:29 -06:00
Daniel Supernault
1f45580f18
Update AP Inbox 2021-09-04 00:11:20 -06:00
daniel
373c5ec691
Merge pull request #2897 from pixelfed/staging
Fix story object route
2021-09-03 23:57:49 -06:00
Daniel Supernault
4c662ff55c
Fix story object route 2021-09-03 23:57:07 -06:00
daniel
cbeab42f54
Merge pull request #2896 from pixelfed/staging
Staging
2021-09-03 23:01:55 -06:00
Daniel Supernault
6b9197cca6
Update changelog 2021-09-03 23:00:58 -06:00
Daniel Supernault
e73cf531cb
Update InstanceCrawlPipeline, remove unused variable 2021-09-03 23:00:30 -06:00
daniel
f593e2b709
Merge pull request #2895 from pixelfed/staging
Archives, Polls and Stories
2021-09-03 22:52:17 -06:00
Daniel Supernault
588384543b
Update Story model, use immutable datetime 2021-09-03 22:46:06 -06:00
Daniel Supernault
4c5b48810f
Update compiled assets 2021-09-03 22:36:52 -06:00
Daniel Supernault
2d99434e2b
Update web routes 2021-09-03 22:26:39 -06:00
Daniel Supernault
6ca140e5cc
Update nav view 2021-09-03 22:20:55 -06:00
Daniel Supernault
a08c8a29d5
Update image optimizer 2021-09-03 22:09:58 -06:00
Daniel Supernault
2ab032599c
Update db config 2021-09-03 22:09:25 -06:00
Daniel Supernault
dd7262d841
Update StoryController, add StoryComposeController 2021-09-03 22:08:15 -06:00
Daniel Supernault
d0bfefe8d0
Update Media model 2021-09-03 21:25:19 -06:00
Daniel Supernault
d7b6edc018
Update NotificationTransformer 2021-09-03 21:23:43 -06:00
Daniel Supernault
3c8c23a143
Update AP Inbox 2021-09-03 21:21:17 -06:00
Daniel Supernault
c7a5715a60
Add StoryPipeline jobs 2021-09-03 21:18:33 -06:00
Daniel Supernault
0d8d6bc71e
Update FollowerService 2021-09-03 20:51:56 -06:00
Daniel Supernault
6b0b2cfaa5
Update StoryService 2021-09-03 20:51:26 -06:00
Daniel Supernault
b32f4d91c4
Update Snowflake service 2021-09-03 20:51:05 -06:00
Daniel Supernault
f808b7b19d
Story transformers 2021-09-03 20:50:38 -06:00
Daniel Supernault
37054e8393
migrations 2021-09-03 20:47:14 -06:00
Daniel Supernault
da6943daed
Add InstancePipeline and NodeinfoService 2021-09-03 20:45:56 -06:00
Daniel Supernault
7641b73158
Update Timeline, remove recent posts 2021-09-03 20:37:36 -06:00
Daniel Supernault
e5aea490b1
Refactor snowflake id generation to improve randomness 2021-09-01 22:46:57 -06:00
Daniel Supernault
e95b702e23
Add activitypub story validator 2021-09-01 03:34:41 -06:00
Daniel Supernault
e90637098a
Add Bearcap util 2021-09-01 01:21:47 -06:00
Daniel Supernault
0e13ab074c
Update SnowflakeService 2021-09-01 01:17:37 -06:00
Daniel Supernault
942fdf5486
Update components 2021-08-31 01:30:21 -06:00
Daniel Supernault
e5e7839736
Update js resources 2021-08-31 01:16:25 -06:00
Daniel Supernault
29e84fa08c
Update sass 2021-08-31 01:15:10 -06:00
Daniel Supernault
f575fd4d76
Update blade views 2021-08-31 01:12:16 -06:00
Daniel Supernault
bc2d664fdb
Update blade views 2021-08-31 01:09:09 -06:00
Daniel Supernault
ca5d964f5f
Update Profile model 2021-08-31 00:42:14 -06:00
Daniel Supernault
e1a3e26644
Update Story model 2021-08-31 00:41:43 -06:00
Daniel Supernault
2593cdeed9
Update Status model, add poll relation and allow up to 2 urls to autolink 2021-08-31 00:40:41 -06:00
Daniel Supernault
ef8e38298f
Update StatusService 2021-08-31 00:40:07 -06:00
Daniel Supernault
f9194df332
Update PollService 2021-08-31 00:39:32 -06:00
Daniel Supernault
2d93bc8b51
Update NotificationService 2021-08-31 00:39:03 -06:00
Daniel Supernault
07bc5d5c83
Update MediaStorageService 2021-08-31 00:38:43 -06:00
Daniel Supernault
450154e5af
Update MediaService 2021-08-31 00:38:27 -06:00
Daniel Supernault
427f9da33e
Update MediaPathService, change story paths 2021-08-31 00:38:07 -06:00
Daniel Supernault
168c19c5b6
Update LikeService 2021-08-31 00:37:40 -06:00
Daniel Supernault
2fb916c22b
Update InstanceService 2021-08-31 00:37:28 -06:00
Daniel Supernault
ad2db4aea7
Update FollowerService 2021-08-31 00:37:02 -06:00
Daniel Supernault
1c59933c0a
Update ProfileController 2021-08-31 00:35:29 -06:00
Daniel Supernault
a4a1270e70
Add Stories to admin dashboard 2021-08-31 00:33:04 -06:00
Daniel Supernault
0573213093
Add StoryService 2021-08-31 00:29:17 -06:00
Daniel Supernault
9a81a69fbb
Update compiled assets 2021-08-31 00:25:13 -06:00
Daniel Supernault
a0da80bc70
Update media gc command 2021-08-31 00:24:20 -06:00
Daniel Supernault
fee2857deb
Update ComposeController 2021-08-31 00:22:08 -06:00
Daniel Supernault
bdc5abbfbc
Update horizon config 2021-08-31 00:18:19 -06:00
Daniel Supernault
07e8ddf8eb
Add new horizon queue 2021-08-30 00:38:12 -06:00
Daniel Supernault
e1277d4081
Update StatusStatelessTransformer, cast snowflake ids as strings 2021-08-27 20:37:00 -06:00
Daniel Supernault
6832836c55
Update changelog 2021-08-27 20:36:01 -06:00
Daniel Supernault
d5e5644dbc
Migrations 2021-08-27 20:34:47 -06:00
Daniel Supernault
fb652805dd
Migrations 2021-08-27 20:33:30 -06:00
Daniel Supernault
bc53ac2af8
Update polls migration, add group support 2021-08-10 22:25:42 -06:00
Daniel Supernault
7709220074
Add Polls 2021-08-04 20:29:21 -06:00
Daniel Supernault
5916f8c76a
Update Profile model, fix getAudienceInbox method 2021-08-04 00:00:50 -06:00
Daniel Supernault
03199e2f68
Update follow intent, fix follower count leak 2021-08-01 15:09:52 -06:00
nogafam.es Admin
e56da35826 Fix PWA implementation to support standard using html link tag 2021-07-27 14:09:17 +02:00
Daniel Supernault
c5281dcdb3
Update PostComponents, re-add time to timestamp 2021-07-27 00:49:59 -06:00
Daniel Supernault
4fb3d1fa70
Update status.reply view, fix archived post leakage 2021-07-27 00:27:36 -06:00
Daniel Supernault
acaf630dee
Update StatusService, invalidate profile embed cache on deletion 2021-07-27 00:13:03 -06:00
Daniel Supernault
3f8acb1266
Update AccountService, add syncPostCount method 2021-07-26 23:59:38 -06:00
Daniel Supernault
b34814e9ff
Update compiled assets 2021-07-26 22:52:46 -06:00
Daniel Supernault
d4921209c5
Update changelog 2021-07-26 22:50:28 -06:00
Daniel Supernault
e9ef0c887a
Add Archive Posts 2021-07-26 22:49:46 -06:00
Daniel Supernault
6e45021fc2
Update StatusTransformer, prioritize scope over deprecated visibility attribute 2021-07-26 22:21:03 -06:00
Daniel Supernault
bc3add0525
Update ContactAdmin mail, set New Support Message subject 2021-07-26 19:23:55 -06:00
Daniel Supernault
15c4fdd90c
Update StatusService, add non-public option and improve cache invalidation 2021-07-26 19:02:11 -06:00
Daniel Supernault
ee0028bc57
Update PublicApiController, use account service 2021-07-26 18:47:40 -06:00
Daniel Supernault
2376580eb7
Update NotificationCard component 2021-07-25 06:15:10 -06:00
Daniel Supernault
ecbc464587
Update changelog 2021-07-25 06:02:28 -06:00
Daniel Supernault
22257cc2a7
Update FollowerService, cache audience 2021-07-25 05:56:35 -06:00
Daniel Supernault
38e5fc43eb
Add FollowObserver 2021-07-25 05:46:42 -06:00
Daniel Supernault
85d5639a52
Update Hashtag component 2021-07-25 05:32:42 -06:00
Daniel Supernault
c4146a3040
Update RemoteProfile component, add follower modals 2021-07-25 05:06:38 -06:00
Daniel Supernault
0e178a3371
Update NotifcationCard.vue component, add refresh button for cold notification cache 2021-07-25 03:33:47 -06:00
Daniel Supernault
7274574c68
Update RemoteProfile, add warning about potentially out of date information 2021-07-25 03:31:47 -06:00
Daniel Supernault
f6131ed764
Update License util, add nameToId method 2021-07-25 03:29:22 -06:00
Daniel Supernault
f3d6023ef8
Update LikeController, improve query perf 2021-07-25 03:19:48 -06:00
Daniel Supernault
14a1367a8f
Federate Media Licenses 2021-07-25 03:17:49 -06:00
Daniel Supernault
b6e226aef9
Update Notification components, fix old notifications with missing attributes 2021-07-25 02:14:41 -06:00
Daniel Supernault
f9516ac316
Update ApiControllers, use NotificationService 2021-07-25 02:12:30 -06:00
Daniel Supernault
bce8edd994
Update PublicApiController, improve accountStatuses api perf 2021-07-25 01:39:03 -06:00
Daniel Supernault
c1f14f89f6
Update FollowPipeline, fix cache invalidation bug 2021-07-25 01:36:57 -06:00
Daniel Supernault
1060dd23d5
Update RemotePost component, update likes reaction bar 2021-07-24 23:37:44 -06:00
Daniel Supernault
7c6cff3103
Update StatusTransformer 2021-07-24 23:24:56 -06:00
Daniel Supernault
1054b025b1
Update StatusTransformer 2021-07-24 23:16:01 -06:00
Daniel Supernault
09d5198c55
Update StatusTransformers, remove includes and use cached services 2021-07-24 23:10:44 -06:00
Daniel Supernault
8001ad998c
Update compiled assets 2021-07-24 22:21:27 -06:00
Daniel Supernault
7314065a2a
Update changelog 2021-07-24 22:20:44 -06:00
Daniel Supernault
2a791f1991
Update ApiV1Controller, add default license support 2021-07-24 22:20:05 -06:00
Daniel Supernault
833d110c9e
Update changelog 2021-07-24 22:14:58 -06:00
Daniel Supernault
ea0fc90c92
Add default licenses and license sync 2021-07-24 22:13:14 -06:00
Daniel Supernault
072d55d1a8
Update Compose Apis, make media descriptions/alt text length limit configurable. Default length: 1000 2021-07-24 21:15:15 -06:00
Daniel Supernault
67e3f6048f
Update Settings, add default license and enforced media descriptions 2021-07-23 09:47:14 -06:00
daniel
eb84b6547d
Merge pull request #2865 from pixelfed/staging
Update LikeController, add UndoLikePipeline and federate Undo Like ac…
2021-07-21 03:45:10 -06:00
Daniel Supernault
27778e00c9
Update changelog 2021-07-21 03:44:57 -06:00
Daniel Supernault
8ac8fcad3f
Update LikeController, add UndoLikePipeline and federate Undo Like activities 2021-07-21 03:41:28 -06:00
daniel
87a6f2cd6f
Merge pull request #2864 from pixelfed/staging
Update ApiController, fix notification bug
2021-07-21 02:17:05 -06:00
Daniel Supernault
f39f32c866
Update ApiController, fix notification bug 2021-07-21 02:16:20 -06:00
daniel
a55d9bf6fd
Merge pull request #2863 from pixelfed/staging
Update Timeline, make text-only posts opt-in by default
2021-07-21 02:03:50 -06:00
Daniel Supernault
aa5b090af8
Update changelog 2021-07-21 02:03:16 -06:00
Daniel Supernault
0153ed6d64
Update Timeline, make text-only posts opt-in by default 2021-07-21 02:00:57 -06:00
daniel
d6d6e6dc75
Merge pull request #2862 from pixelfed/staging
Update PublicApiController, remove text only posts from Public timeline
2021-07-21 01:20:24 -06:00
Daniel Supernault
08f492bd3d
Update PublicApiController, remove text only posts 2021-07-21 01:19:53 -06:00
daniel
b8864b9ae2
Merge pull request #2861 from pixelfed/staging
Staging
2021-07-21 01:18:33 -06:00
Daniel Supernault
68509e4f4f
Update compiled assets 2021-07-21 01:17:40 -06:00
Daniel Supernault
53a80255d0
Update changelog 2021-07-21 01:17:04 -06:00
Daniel Supernault
b0257be20e
Update RemotePost.vue, improve text only post UI 2021-07-21 01:16:37 -06:00
Daniel Supernault
86219b57fc
Update PublicApiController, filter out text replies on home timeline 2021-07-21 01:03:43 -06:00
daniel
a4b697741b
Merge pull request #2860 from pixelfed/staging
Staging
2021-07-20 23:49:37 -06:00
Daniel Supernault
5fb187a062
Update changelog 2021-07-20 23:48:44 -06:00
Daniel Supernault
2fea6ed9ec
Update compiled assets 2021-07-20 23:48:33 -06:00
Daniel Supernault
ed14ee48e1
Update StatusCard, add text support 2021-07-20 23:47:19 -06:00
daniel
e86b4d8007
Merge pull request #2859 from pixelfed/staging
Staging
2021-07-20 23:17:26 -06:00
Daniel Supernault
9f4f983f27
Update ap helpers, set text type when appropriate 2021-07-20 22:17:31 -06:00
Daniel Supernault
f392fa4ed4
Update nav 2021-07-20 21:52:49 -06:00
Daniel Supernault
32268efbc9
Update changelog 2021-07-20 18:15:36 -06:00
Daniel Supernault
4778053f88
Update compiled assets 2021-07-20 18:15:30 -06:00
Daniel Supernault
11eb6acdaf
Update Timeline, fix empty timeline card 2021-07-20 18:13:17 -06:00
Daniel Supernault
7ed65fc9fe
Update PostComponent, use profileUrl method for comments 2021-07-20 18:06:57 -06:00
shynome
e825d73b9a add zh-cn lang 2021-07-18 18:19:11 +08:00
daniel
dd5d767879
Merge pull request #2857 from pixelfed/staging
Update Timeline.vue, improve followed hashtags
2021-07-16 01:26:01 -06:00
Daniel Supernault
c571bd90ef
Update compiled assets 2021-07-16 01:24:45 -06:00
Daniel Supernault
d32ef1dc9d
Update changelog 2021-07-16 01:24:31 -06:00
Daniel Supernault
728f10d778
Update Timeline.vue, improve followed hashtags 2021-07-16 01:20:21 -06:00
daniel
26ca28b4fd
Merge pull request #2856 from pixelfed/staging
Staging
2021-07-15 23:43:35 -06:00
Daniel Supernault
a2494da95a
Update compiled assets 2021-07-15 23:39:45 -06:00
Daniel Supernault
91682473a5
Update changelog 2021-07-15 23:37:55 -06:00
Daniel Supernault
49d27caae8
Update remote profile components 2021-07-15 23:37:23 -06:00
Daniel Supernault
916e8f7111
Update Timeline.vue, increase pagination limit from 3 to 12 and add empty feed placeholder 2021-07-15 23:36:44 -06:00
Daniel Supernault
86422c81b7
Update presenters, improve content warnings 2021-07-15 23:35:34 -06:00
Daniel Supernault
8544bcbda6
Update ContextMenu, add View Profile link 2021-07-15 23:18:40 -06:00
Daniel Supernault
ca6e491c83
Update PublicApiController, use fUserFilterService in public timeline endpoint 2021-07-15 22:39:40 -06:00
daniel
f33422e758
Merge pull request #2855 from pixelfed/staging
Staging
2021-07-15 20:53:02 -06:00
Daniel Supernault
2fe676aa55
Update changelog 2021-07-15 20:50:27 -06:00
Daniel Supernault
135474ae11
Update DiscoverController, use UserFilterService on trendingApi 2021-07-15 20:48:39 -06:00
daniel
e4b47d001c
Merge pull request #2853 from pixelfed/staging
Staging
2021-07-13 23:31:27 -06:00
Daniel Supernault
51bde8ff33
Update changelog 2021-07-13 23:10:25 -06:00
Daniel Supernault
7f4213924f
Update job queue, separate deletes into their own queue 2021-07-13 23:09:50 -06:00
Daniel Supernault
3b071e56ac
Update Timeline, fix padding 2021-07-12 10:47:24 -06:00
Daniel Supernault
c8824d1b51
Update FollowerService 2021-07-11 07:43:29 -06:00
Daniel Supernault
ff48400a38
Update components, add fallback default avatar 2021-07-11 06:07:05 -06:00
Daniel Supernault
f9ba7ed57a
Update changelog 2021-07-11 03:08:34 -06:00
Daniel Supernault
fc93cba96e
Update compiled assets 2021-07-11 03:07:57 -06:00
Daniel Supernault
726553f552
Update components, add fallback default avatar 2021-07-11 02:57:14 -06:00
Daniel Supernault
57e0a741ab
Add FanoutDeletePipeline 2021-07-09 02:07:14 -06:00
Daniel Supernault
5fb33772c3
Update reply blade view, fix missing avatar and media images 2021-07-08 23:23:56 -06:00
daniel
ae995bf4d3
Merge pull request #2850 from pixelfed/staging
Staging
2021-07-08 22:29:56 -06:00
Daniel Supernault
5634ba179d
Update changelog 2021-07-08 22:28:52 -06:00
Daniel Supernault
f33247c30a
Update compiled assets 2021-07-08 22:25:24 -06:00
Daniel Supernault
fc56acb835
Update presenters, fix content warning layout 2021-07-08 22:23:55 -06:00
Daniel Supernault
9607243ff7
Update StatusCard.vue, add togglecw events to other presenters 2021-07-08 22:21:47 -06:00
Daniel Supernault
0f542065bd
Update changelog 2021-07-08 22:20:59 -06:00
Daniel Supernault
b37bb426a2
Update NotificationCard, fix typo in mention, share and comments. Fixes #2848 2021-07-08 22:20:28 -06:00
Daniel Supernault
dd8661a09a
Update changelog 2021-07-08 21:31:40 -06:00
Daniel Supernault
26b9c1401c
Update ActivityPub helpers, fix comment threading in statusFetch() method 2021-07-08 21:30:59 -06:00
daniel
52b1a1ca39
Merge pull request #2849 from pixelfed/staging
Update PublicApiController, fix public timeline filtering
2021-07-08 17:35:05 -06:00
Daniel Supernault
9650b668e9
Update PublicApiController, fix public timeline filtering 2021-07-08 17:33:51 -06:00
daniel
567a4b6b04
Merge pull request #2847 from pixelfed/staging
Update PublicTimelineService
2021-07-07 01:59:05 -06:00
Daniel Supernault
08467d52d5
Update PublicTimelineService 2021-07-07 01:58:39 -06:00
daniel
a35759cef1
Merge pull request #2846 from pixelfed/staging
Update StatusEntityLexer, only add specific status types to PublicTimelineService
2021-07-07 01:55:13 -06:00
Daniel Supernault
7b758135a3
Update changelog 2021-07-07 01:54:23 -06:00
Daniel Supernault
1fdcbe5bf9
Update StatusEntityLexer, only add specific status types to PublicTimelineService 2021-07-07 01:53:28 -06:00
daniel
5378031f8b
Merge pull request #2845 from pixelfed/staging
Update Timeline.vue, fix comment button
2021-07-07 01:49:07 -06:00
Daniel Supernault
f09845bc0b
Update changelog 2021-07-07 01:48:42 -06:00
Daniel Supernault
5a01d5f5bd
Update compiled assets 2021-07-07 01:48:31 -06:00
Daniel Supernault
b6b5ce7c76
Update Timeline.vue, fix comment button 2021-07-07 01:47:16 -06:00
daniel
941a5d0d2d
Merge pull request #2844 from pixelfed/staging
Staging
2021-07-07 01:12:10 -06:00
Daniel Supernault
8a683a9285
Update changelog 2021-07-07 01:07:30 -06:00
Daniel Supernault
a9565ad558
Update compiled assets 2021-07-07 01:07:02 -06:00
Daniel Supernault
4fe42e5b57
Update PublicApiController, improve home timeline perf 2021-07-07 01:06:20 -06:00
Daniel Supernault
9017f7c4e1
Update Profile, fix unauthenticated private profiles 2021-07-07 00:40:01 -06:00
Daniel Supernault
1a2e41b1e3
Update status views, remove like counts from status embed 2021-07-07 00:23:43 -06:00
Daniel Supernault
0e7b2617d4
Update compiled assets 2021-07-06 23:49:31 -06:00
Daniel Supernault
2d6c335f1f
Update changelog 2021-07-06 23:40:35 -06:00
Daniel Supernault
42c6121ab9
Update PostComponent, fix MomentUI like counter 2021-07-06 23:40:08 -06:00
Daniel Supernault
2e54643760
Update changelog 2021-07-06 23:36:56 -06:00
Daniel Supernault
f2686cacd2
Update AdminMediaController, improve perf and use simple pagination 2021-07-06 23:36:25 -06:00
Daniel Supernault
4a30ff118d
Update api routes 2021-07-06 21:21:16 -06:00
daniel
83c0275832
Merge pull request #2797 from ahangarha/improve-persian-translation
Improve Persian Translation
2021-07-06 21:20:14 -06:00
daniel
25fb62e43f
Merge pull request #2843 from pixelfed/staging
Update RemotePost.vue, fix content warning button.
2021-07-06 21:15:09 -06:00
Daniel Supernault
7dbf6690f5
Update changelog 2021-07-06 21:14:29 -06:00
Daniel Supernault
fe5a0d2697
Update compiled assets 2021-07-06 21:14:15 -06:00
Daniel Supernault
7647e724bc
Update RemotePost.vue, fix content warning button 2021-07-06 21:12:37 -06:00
daniel
d2545b2850
Merge pull request #2842 from pixelfed/staging
API perf improvements
2021-07-06 20:45:55 -06:00
Daniel Supernault
3a0d88e2b4
Update changelog 2021-07-06 20:43:53 -06:00
Daniel Supernault
352aa57346
Update ApiV1Controller, use ProfileService for verify_credentials 2021-07-06 20:43:24 -06:00
Daniel Supernault
f67c67bce1
Update ApiV1Controller, use PublicTimelineService 2021-07-06 20:29:52 -06:00
Daniel Supernault
d43e6d8d07
Update NotificationService, use zrevrangebyscore for api 2021-07-06 20:10:42 -06:00
Daniel Supernault
76b6ec2aa6
Update admin instance view 2021-07-06 19:36:43 -06:00
Daniel Supernault
918016a5ad
Update PublicTimelineService 2021-07-06 02:01:24 -06:00
daniel
2ee4b5f853
Merge pull request #2838 from pixelfed/staging
Update PublicApiController
2021-07-05 23:46:11 -06:00
Daniel Supernault
562726bc17
Update changelog 2021-07-05 23:44:15 -06:00
Daniel Supernault
38726d699f
Update compiled assets 2021-07-05 23:43:33 -06:00
Daniel Supernault
950fc27773
Update components 2021-07-05 23:42:46 -06:00
Daniel Supernault
51a277e1ae
Update StatusHashtagService, fix null status bug 2021-07-05 23:40:54 -06:00
Daniel Supernault
f215ee26b3
Update moderator api, expire cached status 2021-07-05 23:00:59 -06:00
Daniel Supernault
78529cb1f8
Update PublicApiController 2021-07-02 03:05:33 -06:00
daniel
2158b2ffdf
Merge pull request #2837 from pixelfed/staging
Staging
2021-07-02 01:45:34 -06:00
Daniel Supernault
af19dd7bb0
Update compiled assets 2021-07-02 01:36:16 -06:00
Daniel Supernault
2bdb638c4f
Update Timeline component, add reaction bar back to network timeline 2021-07-02 01:36:01 -06:00
Daniel Supernault
645f0eef08
Update changelog 2021-07-02 01:35:26 -06:00
Daniel Supernault
82895591c3
Update PublicApiController, add LikeService to Network timeline 2021-07-02 01:34:35 -06:00
Daniel Supernault
d3157f2a2d
Add LikeObserver 2021-07-02 01:33:49 -06:00
Daniel Supernault
447e44e5ac
Update LikeService 2021-07-02 01:31:45 -06:00
Daniel Supernault
117b8410eb
Update PublicTimelineService, skip adds for now 2021-07-02 01:21:21 -06:00
daniel
3c1c7a3b26
Merge pull request #2836 from pixelfed/staging
Add diagnostics to error page and admin dashboard
2021-07-01 21:49:58 -06:00
Daniel Supernault
52ed8bf4ea
Add changelog 2021-07-01 21:47:48 -06:00
Daniel Supernault
64725ecce7
Add diagnostics to error page and admin dashboard 2021-07-01 21:46:45 -06:00
daniel
28f9fe588a
Merge pull request #2835 from pixelfed/staging
Staging
2021-07-01 17:31:29 -06:00
Daniel Supernault
2a38201acb
Update compiled assets 2021-07-01 17:27:07 -06:00
Daniel Supernault
53ae9ca0d9
Update changelog 2021-07-01 17:26:50 -06:00
Daniel Supernault
3cffdb116f
Update ContextMenu, add missing statusUrl method 2021-07-01 17:26:14 -06:00
daniel
a8507dd813
Merge pull request #2833 from pixelfed/staging
Update ApiController, fix hashtag endpoint
2021-06-30 17:11:07 -06:00
Daniel Supernault
9ac8bbde91
Update ApiController, fix hashtag endpoint 2021-06-30 17:10:33 -06:00
daniel
80a3bc1461
Merge pull request #2830 from pixelfed/staging
Staging
2021-06-29 03:07:29 -06:00
Daniel Supernault
076ec333a9
Update compiled assets 2021-06-29 03:06:21 -06:00
Daniel Supernault
8ca685e6ab
Update changelog 2021-06-29 03:06:06 -06:00
Daniel Supernault
e9c46bab75
Update PostComponent, show like count to owner using MomentUI 2021-06-29 03:04:56 -06:00
daniel
8513b6d1e5
Merge pull request #2829 from pixelfed/staging
Staging
2021-06-29 02:17:06 -06:00
Daniel Supernault
c0a2f88b49
Update compiled assets 2021-06-29 02:15:30 -06:00
Daniel Supernault
bae8075ef1
Update changelog 2021-06-29 02:15:15 -06:00
Daniel Supernault
6d956a86f4
Add mark as spammer mod tool, unlists and applies content warning to existing and future posts 2021-06-29 02:14:22 -06:00
Daniel Supernault
7b9e0eefd3
Update InternalApiController formatting 2021-06-29 01:46:04 -06:00
daniel
48dd65144d
Merge pull request #2828 from pixelfed/staging
Staging
2021-06-29 01:30:28 -06:00
Daniel Supernault
a90dc247e9
Update changelog 2021-06-29 01:29:04 -06:00
Daniel Supernault
5be8a36ab2
Update compiled assets 2021-06-29 01:28:24 -06:00
Daniel Supernault
0deaafc0dc
Update Network Timeline, use existing Timeline component 2021-06-29 01:27:35 -06:00
Daniel Supernault
e5f683fda4
Update PublicApiController, improve network timeline perf 2021-06-29 01:23:24 -06:00
Daniel Supernault
1e3d3a69d2
Update Timeline, disable new post update checker and hide reaction bar on network timeline 2021-06-29 01:03:47 -06:00
daniel
66605c0a3d
Merge pull request #2827 from pixelfed/staging
Add UndoAnnounce transformer
2021-06-28 23:26:14 -06:00
Daniel Supernault
3ba31bdc7c
Add UndoAnnounce transformer 2021-06-28 23:25:49 -06:00
daniel
b79e395284
Merge pull request #2826 from pixelfed/staging
Update Timeline, fix suggested posts
2021-06-28 23:20:12 -06:00
Daniel Supernault
c9d1c8e937
Update compiled assets 2021-06-28 23:19:31 -06:00
Daniel Supernault
b530a3bd08
Update changelog 2021-06-28 23:18:56 -06:00
Daniel Supernault
3ba5c88c35
Update Timeline, fix suggested posts 2021-06-28 23:18:28 -06:00
daniel
d9578fa56d
Merge pull request #2825 from pixelfed/staging
Staging
2021-06-28 22:58:52 -06:00
Daniel Supernault
c1a6f7c4d2
Update changelog 2021-06-28 22:53:57 -06:00
Daniel Supernault
dbe730236a
Update compiled assets 2021-06-28 22:53:47 -06:00
Daniel Supernault
858f3f9e0c
Update Timeline component, abstracted reusable partials 2021-06-28 22:51:51 -06:00
Daniel Supernault
a0e83468e9
Update PostComponent, fix formatting 2021-06-28 22:50:40 -06:00
Daniel Supernault
308acc91d3
Update NetworkTimeline, fix remote comment urls 2021-06-28 22:49:22 -06:00
Daniel Supernault
f2fb64ecd4
Add component partials 2021-06-28 22:48:23 -06:00
Daniel Supernault
c8e40e0fd3
Update SharePipeline, add Undo->Announce support 2021-06-28 22:37:38 -06:00
Daniel Supernault
f1208de0ef
Update ApiController, fix nulls in hashtag endpoint 2021-06-28 21:01:45 -06:00
Daniel Supernault
d48ebb829c
Update StatusController, improve share api perf (11s to 72ms) 2021-06-28 20:19:57 -06:00
daniel
d2ff41db05
Merge pull request #2817 from pixelfed/staging
Staging
2021-06-24 20:35:52 -06:00
Daniel Supernault
3741c76da3
Update LikeService, skip self likes 2021-06-23 21:26:45 -06:00
Daniel Supernault
cc47243733
Update Inbox 2021-06-23 21:21:33 -06:00
daniel
5ee61f7c80
Merge pull request #2815 from pixelfed/staging
Staging
2021-06-23 20:02:57 -06:00
Daniel Supernault
c8eb326ef4
Update changelog 2021-06-23 20:01:24 -06:00
Daniel Supernault
8f7f1956fb
Update composer deps 2021-06-23 19:57:52 -06:00
Daniel Supernault
929ff5eb01
Update Inbox, fix tombstone bug 2021-06-23 19:57:39 -06:00
daniel
a6ea480f84
Merge pull request #2811 from pixelfed/staging
Staging
2021-06-18 06:21:40 -06:00
Daniel Supernault
5a3384d0e4
wip 2021-06-18 06:19:45 -06:00
Daniel Supernault
49d68cdd96
Update compiled assets 2021-06-18 06:19:36 -06:00
Daniel Supernault
9a2db8ebc0
Update Activity component, fix comment bug 2021-06-18 06:18:35 -06:00
daniel
4e86f76ab9
Merge pull request #2810 from pixelfed/staging
Staging
2021-06-18 05:31:33 -06:00
Daniel Supernault
04b6eb94ca
Update compiled assets 2021-06-18 05:30:53 -06:00
Daniel Supernault
34bbb2d1a2
Update changelog 2021-06-18 05:30:03 -06:00
Daniel Supernault
a3a86d46a1
Update NotificationCard, fix missing status bug 2021-06-18 05:29:37 -06:00
Daniel Supernault
ef63124d88
Update Inbox, delete notifications on tombstone 2021-06-18 05:24:26 -06:00
daniel
e24c11c5ff
Merge pull request #2809 from pixelfed/staging
Staging
2021-06-18 05:08:21 -06:00
Daniel Supernault
cfccb6f251
Update changelog 2021-06-18 05:06:39 -06:00
Daniel Supernault
2cc577a6be
Update compiled assets 2021-06-18 05:06:24 -06:00
Daniel Supernault
1fa08644b4
Update Remote Post + Profile hashtag to redirect to local urls 2021-06-18 05:03:40 -06:00
daniel
24905614ff
Merge pull request #2808 from pixelfed/staging
Staging
2021-06-18 02:58:36 -06:00
Daniel Supernault
27fbe88438
Update changelog 2021-06-18 02:58:03 -06:00
Daniel Supernault
c8c6b98380
Update like api, store status_profile_id and is_comment 2021-06-18 02:57:08 -06:00
Daniel Supernault
8edd829436
Update AP Helpers, generate notification for remote replies 2021-06-18 02:55:42 -06:00
daniel
4f2eb712ad
Merge pull request #2807 from pixelfed/staging
Staging
2021-06-17 22:44:39 -06:00
Daniel Supernault
79324679f1
Update compiled assets 2021-06-17 22:42:42 -06:00
Daniel Supernault
78ee44662c
Add PWA + ServiceWorker with offline support 2021-06-17 22:42:08 -06:00
daniel
1ede2ef8c1
Merge pull request #2805 from pixelfed/staging
Staging
2021-06-13 02:16:52 -06:00
Daniel Supernault
22c321a998
Update changelog 2021-06-13 02:16:25 -06:00
Daniel Supernault
f0bb79e249
Update compiled assets 2021-06-13 02:16:14 -06:00
Daniel Supernault
02b04a4b4d
Update RemoteProfile component, implement pagination 2021-06-13 01:52:05 -06:00
daniel
963bea595b
Merge pull request #2804 from pixelfed/staging
Staging
2021-06-13 00:49:12 -06:00
Daniel Supernault
ab985167f9
Update readme 2021-06-13 00:48:51 -06:00
Daniel Supernault
4abb2b1e92
Update compiled assets 2021-06-13 00:42:49 -06:00
Daniel Supernault
0e0d50aa8f
Update changelog 2021-06-13 00:39:07 -06:00
Daniel Supernault
f37952d6cf
Update verify email screen, add contact admin link 2021-06-13 00:37:54 -06:00
Daniel Supernault
d0ddb4e78e
Update StoryCompose component 2021-06-13 00:19:59 -06:00
Daniel Supernault
6e56dbed1a
Update Profile component, fix remote urls 2021-06-13 00:19:01 -06:00
daniel
79d792dacc
Merge pull request #2803 from pixelfed/staging
Update StoryController, fix expiration time bug
2021-06-12 20:02:00 -06:00
Daniel Supernault
1e424f023a
Update changelog 2021-06-12 20:01:31 -06:00
Daniel Supernault
39e57f9506
Update StoryController, fix expiration time bug 2021-06-12 20:00:59 -06:00
daniel
95037d4d88
Merge pull request #2800 from pixelfed/staging
Update routes, add legacy webfinger profile redirect
2021-06-11 00:06:04 -06:00
Daniel Supernault
174260787e
Update changelog 2021-06-11 00:05:15 -06:00
Daniel Supernault
93c7af7464
Update routes, add legacy webfinger profile redirect 2021-06-11 00:03:41 -06:00
daniel
ed3ba496dc
Merge pull request #2799 from pixelfed/staging
Update Profile, add linkified bio, joined date, follows you label and improved website handling
2021-06-10 22:59:20 -06:00
Daniel Supernault
07321b021c
Update changelog 2021-06-10 22:57:28 -06:00
Daniel Supernault
afe901fffa
Update compiled assets 2021-06-10 22:57:15 -06:00
Daniel Supernault
8ee104363a
Update Profile, add linkified bio, joined date, follows you label and improved website handling 2021-06-10 22:54:31 -06:00
daniel
8dc6d17bfd
Merge pull request #2798 from pixelfed/staging
Auto Following support for admins
2021-06-10 21:11:33 -06:00
Daniel Supernault
fbd77f4410
Update changelog 2021-06-10 21:08:28 -06:00
Daniel Supernault
68aa25400b
Add Auto Following support for admins 2021-06-10 21:07:35 -06:00
Mostafa Ahangarha
2e2a946c64 improve persian translation 2021-06-10 13:29:26 +04:30
daniel
7a234ccd4c
Merge pull request #2791 from pixelfed/staging
Add WebP support
2021-06-08 03:28:48 -06:00
Daniel Supernault
9928bfc273
Update changelog 2021-06-08 03:22:46 -06:00
Daniel Supernault
069a0e4ae1
Add WebP support 2021-06-08 03:22:01 -06:00
daniel
b46035df87
Merge pull request #2790 from pixelfed/staging
Update Activity component, add at (@) symbol for remote profiles and local urls for remote posts and profile
2021-06-08 01:50:20 -06:00
Daniel Supernault
1baf378497
Update changelog 2021-06-08 01:49:26 -06:00
Daniel Supernault
b230834d25
Update compiled assets 2021-06-08 01:49:18 -06:00
Daniel Supernault
a2211815d4
Update Activity component, add at (@) symbol for remote profiles and local urls for remote posts and profile 2021-06-08 01:48:33 -06:00
daniel
23e46bcfd1
Merge pull request #2789 from pixelfed/staging
Update Notification component, add at (@) symbol for remote profiles and local urls for remote posts and profiles
2021-06-08 01:32:41 -06:00
Daniel Supernault
a20931eeb4
Update changelog 2021-06-08 01:31:07 -06:00
Daniel Supernault
06e7ef16c6
Update compiled assets 2021-06-08 01:30:53 -06:00
Daniel Supernault
aafd6a21a5
Update Notification component, add at (@) symbol for remote profiles and local urls for remote posts and profiles 2021-06-08 01:29:44 -06:00
daniel
004eff1625
Merge pull request #2788 from pixelfed/staging
Staging
2021-06-07 23:34:53 -06:00
Daniel Supernault
c27da498db
Update changelog 2021-06-07 23:33:36 -06:00
Daniel Supernault
65259e1135
Update compiled assets 2021-06-07 23:33:20 -06:00
Daniel Supernault
66750d347a
Update Timeline, implement suggested post opt out 2021-06-07 23:32:35 -06:00
Daniel Supernault
549202948d
Update landing page, use config_cache 2021-06-07 19:28:26 -06:00
daniel
658411f8b4
Merge pull request #2787 from pixelfed/staging
Staging
2021-06-07 17:36:56 -06:00
Daniel Supernault
2d5a259e6f
Update changelog 2021-06-07 17:18:56 -06:00
Daniel Supernault
20ec870bf9
Update PrettyNumber, fix deprecated warning 2021-06-07 17:18:06 -06:00
daniel
a34fe00114
Merge pull request #2776 from pixelfed/staging
v0.11.0
2021-06-01 23:54:19 -06:00
Daniel Supernault
91cc9adf52
Update changelog 2021-06-01 23:50:32 -06:00
Daniel Supernault
a4121cb8e4
Bump version 2021-06-01 23:50:21 -06:00
Daniel Supernault
b8f4385d40
Update compiled assets 2021-06-01 23:47:01 -06:00
Daniel Supernault
56215be73a
Update Timeline, add recent home feed 2021-06-01 23:40:34 -06:00
Daniel Supernault
2d0a253e07
Update DirectMessageController, disable exception logging for invalid urls. Fixes #2752 2021-06-01 21:18:02 -06:00
Daniel Supernault
69567e19df
Update NotificationCard, fix loading bug 2021-06-01 21:01:00 -06:00
Daniel Supernault
4662afd248
Update pixelfed config formatting 2021-06-01 20:09:25 -06:00
Daniel Supernault
7d24560dd0
Update app config, change default descriptions 2021-06-01 20:07:52 -06:00
Daniel Supernault
84520fe103
Update PrettyNumber, add decimal option 2021-06-01 20:07:07 -06:00
Daniel Supernault
28df9f7e80
Update Inbox, fix reply/comment bug by moving attachment validation to Note with attachments 2021-06-01 20:06:18 -06:00
Daniel Supernault
1e230e80fb
Update PublicApiController, add recent feed support to home timeline 2021-06-01 20:03:59 -06:00
Daniel Supernault
f3bf2fd41e
Update StatusController, add cache invalidation for timeline cursor 2021-06-01 19:51:22 -06:00
Daniel Supernault
7cbd6bc36d
Update StatusController, use transactions for status views 2021-05-31 23:54:20 -06:00
Daniel Supernault
e3899d3684
Fix migration sqlite support 2021-05-31 23:26:40 -06:00
daniel
014d6144f8
Merge pull request #2775 from pixelfed/staging
Update liked by, fix remote username urls
2021-05-31 22:46:15 -06:00
Daniel Supernault
cbdfb69436
Update compiled assets 2021-05-31 22:45:16 -06:00
Daniel Supernault
0affcb0a68
Update changelog 2021-05-31 22:44:43 -06:00
Daniel Supernault
f767d99ad5
Update liked by, fix remote username urls 2021-05-31 22:43:53 -06:00
daniel
d230ab9aa4
Merge pull request #2774 from pixelfed/staging
Staging
2021-05-31 21:52:15 -06:00
Daniel Supernault
d496a48574
Add admin.js, fixes #2772 2021-05-31 21:51:23 -06:00
Daniel Supernault
821b58bbb3
Update composer.lock deps 2021-05-31 21:50:18 -06:00
Daniel Supernault
3c1e1f7e87
Update PublicApiController, fix likes bug 2021-05-31 21:49:44 -06:00
daniel
bbb1ffe821
Merge pull request #2771 from pixelfed/staging
Staging
2021-05-27 23:09:31 -06:00
Daniel Supernault
525dce1c59
Update changelog 2021-05-27 23:07:30 -06:00
Daniel Supernault
7e0be15404
Update exceptions handler 2021-05-27 23:06:47 -06:00
Daniel Supernault
178ed63d0a
Update AuthServiceProvider, increase default token + refresh token lifetime 2021-05-27 23:04:24 -06:00
Daniel Supernault
0788bffa37
Update Timeline component, show counts and make sidebar footer lighter 2021-05-26 20:31:11 -06:00
Daniel Supernault
78ad4e77d9
Update NotificationCard component, fix default value 2021-05-26 20:29:33 -06:00
Daniel Supernault
224bda1760
Update changelog 2021-05-26 20:12:24 -06:00
Daniel Supernault
c8e43c6094
Update api, remove auth requirement for hashtag timeline 2021-05-26 20:11:25 -06:00
daniel
bfe6969dae
Merge pull request #2770 from pixelfed/staging
Update AdminStatsService, fix postgres bug
2021-05-25 01:37:13 -06:00
Daniel Supernault
55c3eaf26d
Update changelog 2021-05-25 01:36:45 -06:00
Daniel Supernault
af71913597
Update AdminStatsService, fix postgres bug 2021-05-25 01:36:13 -06:00
daniel
f68b93a37f
Merge pull request #2769 from pixelfed/staging
New admin dashboard layout
2021-05-24 23:36:21 -06:00
Daniel Supernault
b971f07e8c
Update changelog 2021-05-24 23:26:17 -06:00
Daniel Supernault
117ac9a286
Update compiled assets 2021-05-24 23:25:59 -06:00
Daniel Supernault
eb7d5a4e36
New admin dashboard layout 2021-05-24 23:24:42 -06:00
daniel
d8a169870e
Merge pull request #2768 from pixelfed/staging
Staging
2021-05-24 19:42:35 -06:00
Daniel Supernault
53730e493f
Update package-lock 2021-05-24 18:21:59 -06:00
Daniel Supernault
3de44f3392
Update Timeline.vue, batch api views 2021-05-24 00:37:52 -06:00
daniel
b654d68833
Merge pull request #2767 from pixelfed/staging
Staging
2021-05-23 23:13:41 -06:00
Daniel Supernault
444c6d5163
Update changelog 2021-05-23 23:09:59 -06:00
Daniel Supernault
92dc7af69b
Update landing and about page 2021-05-23 23:09:26 -06:00
Daniel Supernault
061b145b54
Update ComposeController, bail on empty attachments 2021-05-20 20:26:26 -06:00
daniel
446ccc6a58
Merge pull request #2762 from pixelfed/staging
Staging
2021-05-19 02:20:28 -06:00
Daniel Supernault
29947e157c
Update admin settings 2021-05-19 02:15:37 -06:00
Daniel Supernault
24e77f647d
Update admin settings 2021-05-19 02:01:33 -06:00
Daniel Supernault
41792eea56
Update config() to config_cache() 2021-05-18 22:45:04 -06:00
daniel
50fad248cb
Merge pull request #2761 from pixelfed/staging
Staging
2021-05-18 19:49:14 -06:00
Daniel Supernault
e8f15b6fa1
Update changelog 2021-05-18 19:47:47 -06:00
Daniel Supernault
241ae0368f
Add hashtag timeline to v1 api 2021-05-18 19:46:26 -06:00
Daniel Supernault
dac326e949
Update StatusTransformer, fix missing tags attribute 2021-05-18 19:39:33 -06:00
Daniel Supernault
955696b8a0
Update site config, fix boolean casting 2021-05-18 17:53:59 -06:00
daniel
08dd3bf66a
Merge pull request #2759 from pixelfed/staging
Staging
2021-05-14 17:16:34 -06:00
Daniel Supernault
74261999d3
Update changelog 2021-05-14 17:15:40 -06:00
Daniel Supernault
c9abd70e8a
Update LikeService, fix authentication bug 2021-05-14 17:15:08 -06:00
Daniel Supernault
3b4add7b8e
Update changelog 2021-05-13 23:46:38 -06:00
Daniel Supernault
a4efbb75d8
Update admin settings, add rules 2021-05-13 23:45:36 -06:00
daniel
f960f63b6f
Merge pull request #2757 from pixelfed/staging
Staging
2021-05-12 22:21:52 -06:00
Daniel Supernault
73d9799a38
Update changelog 2021-05-12 22:21:22 -06:00
Daniel Supernault
de117050c3
Update compiled assets 2021-05-12 22:21:07 -06:00
Daniel Supernault
04094baaef
Update PostComponent, show like count to status owner 2021-05-12 22:20:46 -06:00
Daniel Supernault
4408e2ef8c
Update LikeService, show like count to status owner 2021-05-12 22:18:00 -06:00
Daniel Supernault
f47161fcd9
Update StatusDeletePipeline 2021-05-12 22:16:35 -06:00
daniel
c50a640368
Merge pull request #2756 from pixelfed/staging
Update composer.json
2021-05-11 23:57:00 -06:00
Daniel Supernault
f2be7ea551
Update composer.json 2021-05-11 23:56:33 -06:00
daniel
bb545db5b2
Merge pull request #2755 from pixelfed/staging
Update admin settings
2021-05-11 23:48:06 -06:00
Daniel Supernault
68a21de57c
Update admin settings 2021-05-11 23:47:29 -06:00
daniel
9a88d645ab
Merge pull request #2754 from pixelfed/staging
Update AuthServiceProvider
2021-05-11 23:45:33 -06:00
Daniel Supernault
868cea96ca
Update AuthServiceProvider 2021-05-11 23:45:01 -06:00
daniel
70e3fed0f2
Merge pull request #2753 from pixelfed/staging
Staging
2021-05-11 23:40:57 -06:00
Daniel Supernault
e379ae2cbd
Update admin settings, add warning about enabling config cache 2021-05-11 23:39:55 -06:00
Daniel Supernault
d2cb7d4770
Update ConfigCacheService, fix db issue 2021-05-11 23:33:08 -06:00
Daniel Supernault
b9054f6fd2
Update changelog 2021-05-11 23:26:30 -06:00
Daniel Supernault
377f9d65a8
Update compiled assets 2021-05-11 23:26:22 -06:00
Daniel Supernault
fabb57a9d5
Add profile pronouns 2021-05-11 23:25:10 -06:00
Daniel Supernault
8a73643277
Update PublicApiController, increase public timeline to 6 months from 3 2021-05-11 22:12:10 -06:00
Daniel Supernault
813648cfbb
Update admin settings views 2021-05-11 22:11:10 -06:00
Daniel Supernault
a3a761c348
Update app layout 2021-05-11 22:09:36 -06:00
Daniel Supernault
d60f5c314a
Update changelog 2021-05-11 22:08:47 -06:00
Daniel Supernault
f2066b7401
Add admin config settings 2021-05-11 22:07:55 -06:00
Daniel Supernault
7895097fc1
Update config() to config_cache() 2021-05-11 20:14:51 -06:00
Daniel Supernault
3a9203e039
Update config() to config_cache() 2021-05-11 20:03:29 -06:00
Daniel Supernault
f4fc8347c9
Update config() to config_cache() 2021-05-11 19:59:24 -06:00
Daniel Supernault
7873b7ecc5
Update config() to config_cache() 2021-05-11 19:08:08 -06:00
Daniel Supernault
c65d03788b
Update config() to config_cache() 2021-05-11 19:07:03 -06:00
Daniel Supernault
1c2baa8f2c
Update config() to config_cache() 2021-05-11 18:30:32 -06:00
Daniel Supernault
3e52458889
Update config() to config_cache() 2021-05-11 18:26:52 -06:00
Daniel Supernault
53134208fe
Update config() to config_cache() 2021-05-11 18:17:03 -06:00
Daniel Supernault
27b722e7a7
Update config() to config_cache() 2021-05-10 23:23:09 -06:00
Daniel Supernault
c0e693cc73
Update config() to config_cache() 2021-05-10 21:11:43 -06:00
Daniel Supernault
a9f009305c
Update config() to config_cache() 2021-05-10 21:04:23 -06:00
Daniel Supernault
1d54204635
Update config() to config_cache() 2021-05-10 20:04:13 -06:00
Daniel Supernault
e7b33c4cc6
Update app config 2021-05-07 22:22:00 -06:00
Daniel Supernault
35b92fd7c9
Update admin settings 2021-05-07 22:19:58 -06:00
Daniel Supernault
61254b907f
Update site name config 2021-05-07 21:47:51 -06:00
Daniel Supernault
c6848e99a4
Add helpers 2021-05-07 21:37:10 -06:00
Daniel Supernault
3136f8e137
Add ConfigCacheService 2021-05-07 21:34:58 -06:00
Daniel Supernault
ba37a54a20
Add ConfigCache model and migration 2021-05-07 21:32:59 -06:00
daniel
4ad3de3b88
Merge pull request #2747 from pixelfed/staging
Update LikeService, fix likedBy method
2021-05-03 17:57:56 -06:00
Daniel Supernault
5d2f575984
Update changelog 2021-05-03 17:55:34 -06:00
Daniel Supernault
a5e64da69b
Update LikeService, fix likedBy method 2021-05-03 17:55:06 -06:00
daniel
67f7a75bd0
Merge pull request #2746 from pixelfed/staging
Staging
2021-05-01 16:17:20 -06:00
Daniel Supernault
c10a94649c
Update changelog 2021-05-01 16:12:33 -06:00
Daniel Supernault
b1cde9840c
Update compiled assets 2021-05-01 16:12:12 -06:00
Daniel Supernault
7bcbf96bee
Update Timeline component, change like logic 2021-05-01 16:10:03 -06:00
Daniel Supernault
0a35f5d636
Update PostComponent, change like logic 2021-05-01 16:08:42 -06:00
Daniel Supernault
372bacb01b
Update StatusTransformers, add liked_by attribute 2021-05-01 16:06:39 -06:00
Daniel Supernault
ea68724054
Update LikeController, hide like counts 2021-05-01 16:01:32 -06:00
Daniel Supernault
477db75896
Add LikeService 2021-05-01 15:51:02 -06:00
Daniel Supernault
391b1287ac
Update ProfileService, use account transformer 2021-05-01 13:35:47 -06:00
Daniel Supernault
9cf962fff5
Update Settings, remove reports page 2021-05-01 12:40:32 -06:00
daniel
2e2078a2fe
Merge pull request #2744 from pixelfed/staging
Staging
2021-04-30 23:50:40 -06:00
Daniel Supernault
f2055b4dcd
Update compiled assets 2021-04-30 23:49:39 -06:00
Daniel Supernault
9341f0b38f
Update components, remove like counts 2021-04-30 23:48:36 -06:00
Daniel Supernault
7694487aea
Update changelog 2021-04-30 21:42:23 -06:00
Daniel Supernault
c799a01aa9
Update StatusController, allow license edits without 24 hour limit 2021-04-30 21:41:42 -06:00
Daniel Supernault
4dac1a43dd
Update web routes 2021-04-30 21:38:14 -06:00
daniel
a96f6d96cb
Merge pull request #2743 from pixelfed/staging
Update StoryCompose crop logic
2021-04-30 20:08:34 -06:00
Daniel Supernault
b3e7052426
Update compiled assets 2021-04-30 20:07:15 -06:00
Daniel Supernault
bbad32f73d
Add changelog 2021-04-30 20:04:10 -06:00
Daniel Supernault
2ead622c41
Update StoryCompose crop logic 2021-04-30 20:03:36 -06:00
daniel
2cbb7b9a6b
Merge pull request #2742 from pixelfed/staging
Update story garbage collection
2021-04-30 20:00:17 -06:00
Daniel Supernault
e8e249ce31
Update story garbage collection 2021-04-30 19:59:56 -06:00
daniel
0869c10a37
Merge pull request #2741 from pixelfed/staging
Update StoryController, optimize photo size by resizing to 9:16 aspect
2021-04-30 00:21:05 -06:00
Daniel Supernault
86a1c5a603
Update changelog 2021-04-30 00:20:14 -06:00
Daniel Supernault
e66ed9a222
Update StoryController, optimize photo size by resizing to 9:16 aspect 2021-04-30 00:19:45 -06:00
daniel
f718346031
Merge pull request #2740 from pixelfed/staging
Staging
2021-04-30 00:12:30 -06:00
Daniel Supernault
ca00f2bb97
Update changelog 2021-04-30 00:11:54 -06:00
Daniel Supernault
c2f8faaeee
Update StoryController, fix cache crop bug 2021-04-30 00:10:50 -06:00
daniel
c250bdaaf9
Merge pull request #2739 from pixelfed/staging
Update StoryController
2021-04-29 23:58:29 -06:00
Daniel Supernault
ae46bad70a
Update StoryController 2021-04-29 23:58:06 -06:00
daniel
1b7ef61a4a
Merge pull request #2738 from pixelfed/staging
Staging
2021-04-29 23:55:10 -06:00
Daniel Supernault
40f9aa6055
Update import job 2021-04-29 23:52:59 -06:00
daniel
f25452ea1a
Merge pull request #2642 from stelzch/dev
Create directory in InstagramImport
2021-04-29 23:52:09 -06:00
daniel
bd27ecb68e
Merge pull request #2732 from Wv5twkFEKh54vo4tta9yu7dHa3/staging
Use same json error format as Mastodon
2021-04-29 23:43:42 -06:00
Daniel Supernault
391c841b04
Update changelog 2021-04-29 23:40:10 -06:00
Daniel Supernault
22dddaa044
Update Helpers, fix broken tests 2021-04-29 23:39:38 -06:00
Daniel Supernault
37969da936
Update compiled assets 2021-04-29 23:31:21 -06:00
Daniel Supernault
2814b55b1b
Update changelog 2021-04-29 23:29:30 -06:00
Daniel Supernault
87b0cba027
Update composer.lock 2021-04-29 23:29:13 -06:00
Daniel Supernault
39a7610314
Update StoryCompose component, improve full screen preview 2021-04-29 23:28:26 -06:00
Daniel Supernault
2119965659
Update ComposeModal 2021-04-29 23:27:25 -06:00
Daniel Supernault
668e936eb5
Update instance endpoint, add custom description 2021-04-28 19:26:12 -06:00
Daniel Supernault
c8edca696b
Update Stories, add crop and duration settings to composer 2021-04-27 20:15:31 -06:00
Daniel Supernault
9f380a5597
Add new migration 2021-04-27 20:04:15 -06:00
Daniel Supernault
c43f8bcce8
Update story garbage collection, handle non active stories and new ephemeral story media directory 2021-04-27 20:02:33 -06:00
daniel
a2b8b1d799
Merge pull request #2734 from pixelfed/staging
Update ApiV1Controller, add missing instance api attributes
2021-04-22 22:47:40 -06:00
Daniel Supernault
71aeb8a25d
Update changelog 2021-04-22 22:47:30 -06:00
Daniel Supernault
64b86546b9
Update ApiV1Controller, add missing instance api attributes 2021-04-22 22:46:05 -06:00
daniel
de8ee9f21f
Merge pull request #2733 from pixelfed/staging
Staging
2021-04-20 23:32:58 -06:00
Daniel Supernault
716247857c
Update changelog 2021-04-20 23:18:43 -06:00
Daniel Supernault
66b4f8c773
Update AP Helpers, use instance filtering 2021-04-20 23:18:07 -06:00
Daniel Supernault
c20a9dfefe
Add InstanceService 2021-04-20 23:11:43 -06:00
Daniel Supernault
35393edfef
Update AdminInstanceController, invalidate banned domain cache when updated 2021-04-20 22:31:11 -06:00
Daniel Supernault
e73b2f8373
Update ap helpers 2021-04-20 22:29:52 -06:00
Daniel Supernault
7066e19d0e
Update ap helpers 2021-04-20 22:09:59 -06:00
Daniel Supernault
533cabed47
Update changelog 2021-04-20 22:04:29 -06:00
Daniel Supernault
f582937300
Update admin instance page, add search and improve performance 2021-04-20 22:03:41 -06:00
Daniel Supernault
ab755811d1
Update composer deps 2021-04-20 21:29:06 -06:00
Daniel Supernault
8216c6da88
Update npm deps 2021-04-20 21:25:33 -06:00
Wv5twkFEKh54vo4tta9yu7dHa3
a19d4d5418
Use same json error format as Mastodon
As documented in the Mastodon API ( https://docs.joinmastodon.org/entities/error/ ),   error responses use "error" as the key for the value, instead of Laravel's default (which is "message")
2021-04-20 18:18:42 +02:00
daniel
f526090229
Merge pull request #2725 from pixelfed/staging
Staging
2021-04-07 19:01:00 -06:00
daniel
6a570d07ec
Merge pull request #2723 from pixelfed/dependabot/composer/phpseclib/phpseclib-2.0.31
Bump phpseclib/phpseclib from 2.0.30 to 2.0.31
2021-04-07 16:00:33 -06:00
dependabot[bot]
12282b9819
Bump phpseclib/phpseclib from 2.0.30 to 2.0.31
Bumps [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) from 2.0.30 to 2.0.31.
- [Release notes](https://github.com/phpseclib/phpseclib/releases)
- [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md)
- [Commits](https://github.com/phpseclib/phpseclib/compare/2.0.30...2.0.31)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-07 21:24:06 +00:00
daniel
089ca37457
Merge pull request #2720 from pixelfed/staging
Staging
2021-04-06 23:38:50 -06:00
Daniel Supernault
aa5bb3f7fa
Update changelog 2021-04-06 23:38:27 -06:00
Daniel Supernault
10119bbbea
Update PublicApiController, limit network pagination to 3 months 2021-04-06 23:37:53 -06:00
daniel
4f31d7f391
Merge pull request #2705 from wileyfoxyx/dev
Update and improve Russian translate
2021-04-06 22:01:11 -06:00
daniel
57818c2fe7
Merge pull request #2696 from ZER0-X/dev
Improve and update the Arabic language
2021-04-06 21:53:57 -06:00
Daniel Supernault
898e41cfe1
Fix cs 2021-04-06 21:53:39 -06:00
daniel
56458c1f40
Merge pull request #2588 from dunn/docker-compose-env
docker-compose: store default db passwords in .env.docker
2021-04-06 21:38:14 -06:00
daniel
95b6b78f73
Merge pull request #2677 from hellcp/patch-1
Fix label describing the wrong field in settings
2021-04-06 21:37:40 -06:00
daniel
d87eb0bfb8
Merge pull request #2719 from pixelfed/staging
Add Network Timeline
2021-04-06 21:29:53 -06:00
Daniel Supernault
e5b61be52d
Update network blade view 2021-04-06 21:25:07 -06:00
Daniel Supernault
5fb7ef491c
Update compiled assets 2021-04-06 21:23:44 -06:00
Daniel Supernault
5be02155c1
Update changelog 2021-04-06 21:19:50 -06:00
Daniel Supernault
af7face4da
Add Network Timeline 2021-04-06 21:17:42 -06:00
daniel
d07d236c14
Merge pull request #2711 from pixelfed/staging
New License formats
2021-03-31 22:29:27 -06:00
Daniel Supernault
b9221ffc27
Update changelog 2021-03-31 22:28:33 -06:00
Daniel Supernault
b22a59337f
Update compiled assets 2021-03-31 22:25:23 -06:00
Daniel Supernault
4ad573c8de
Update components 2021-03-31 22:24:59 -06:00
Daniel Supernault
552e950d7a
New License formats 2021-03-31 22:08:03 -06:00
daniel
a368c24d63
Merge pull request #2706 from pixelfed/staging
Staging
2021-03-19 21:24:49 -06:00
Daniel Supernault
22827bbc46
Update changelog 2021-03-19 21:23:32 -06:00
Daniel Supernault
886ea6175d
Update ApiV1Controller, add missing variable 2021-03-19 21:22:37 -06:00
Ilya
78d8799cdc
Fixed translate 2021-03-18 22:39:57 +03:00
Ilya
22fc1a8c64
Added 'profiles' 2021-03-18 22:38:44 +03:00
Ilya
cc5dec2c3e
Update profile.php
fixed translate
2021-03-18 22:37:40 +03:00
Ilya
edf9c565c7
Update passwords.php
Fixed translate
2021-03-18 22:36:36 +03:00
Ilya
53d0298ae1
Update navmenu.php
Fixed translate
2021-03-18 22:33:40 +03:00
Ilya
34aad3608c
Update helpcenter.php
Fixed and added 'taggingPeople'
2021-03-18 22:32:25 +03:00
Ilya
9b27533f49
Create exception.php 2021-03-18 22:30:43 +03:00
daniel
a3eacfde50
Merge pull request #2698 from pixelfed/staging
Staging
2021-03-03 20:22:01 -07:00
Daniel Supernault
42faa9ff7e
Update changelog 2021-03-03 20:19:09 -07:00
Daniel Supernault
e1c6297ee7
Update PublicApiController, show unlisted comments 2021-03-03 20:18:32 -07:00
zer0-x
9f204e4c23 Improve and update the Arabic language 2021-03-02 21:02:44 +03:00
daniel
e25600cdc6
Merge pull request #2695 from pixelfed/staging
Add Autocomplete (hashtags + mentions)
2021-03-01 23:00:11 -07:00
Daniel Supernault
64c86cffa4
Update changelog 2021-03-01 22:57:36 -07:00
Daniel Supernault
be8d36ee46
Update compiled assets 2021-03-01 22:57:21 -07:00
Daniel Supernault
d1d74707a7
Update autocomplete, add dark mode support 2021-03-01 22:52:12 -07:00
Daniel Supernault
de514f7d76
Add Autocomplete for hashtags + mentions 2021-03-01 22:04:46 -07:00
daniel
1a213ae9bc
Merge pull request #2694 from pixelfed/staging
Update Compose Apis, refactor rate limits
2021-03-01 21:15:57 -07:00
Daniel Supernault
77e1115740
Update changelog 2021-03-01 21:02:51 -07:00
Daniel Supernault
c2350f10d7
Update compiled assets 2021-03-01 21:02:45 -07:00
Daniel Supernault
42375b3d79
Update Compose Apis, refactor rate limits 2021-03-01 20:58:35 -07:00
daniel
d2ff146526
Merge pull request #2691 from pixelfed/staging
Staging
2021-03-01 02:21:02 -07:00
Daniel Supernault
f3a2b354db
Update bouncer 2021-03-01 02:20:19 -07:00
daniel
aad77239a9
Merge pull request #2690 from pixelfed/staging
Staging
2021-03-01 00:31:29 -07:00
Daniel Supernault
106964f8ea
Update changelog 2021-03-01 00:29:42 -07:00
Daniel Supernault
5cf8bee47a
Update compiled assets 2021-03-01 00:25:46 -07:00
Daniel Supernault
5f67a48442
Timeline refactor 2021-03-01 00:24:44 -07:00
Daniel Supernault
3f8202e29a
Update PhotoPresenter, add width and height to images 2021-03-01 00:08:10 -07:00
Daniel Supernault
d200c12cf2
Update Nodeinfo util, use last_active_at for monthly active user count 2021-02-28 23:25:37 -07:00
Daniel Supernault
b2501bfcc3
Update moderation api, invalidate profile embed 2021-02-28 23:14:27 -07:00
Daniel Supernault
9c8a87c331
Update StatusController, invalidate profile embed cache on status delete 2021-02-28 23:04:15 -07:00
Daniel Supernault
063558e3ac
Update comment apis 2021-02-28 22:41:07 -07:00
Daniel Supernault
f0e48a09a3
Update ComposeController, add autocomplete apis for hashtags and mentions 2021-02-28 22:38:07 -07:00
Daniel Supernault
001d410579
Update ComposeModal, limit visibility scope for private accounts 2021-02-28 22:35:43 -07:00
Daniel Supernault
77d4353a0a
Update font icons, use font-display:swap 2021-02-28 22:33:55 -07:00
daniel
0ac9fadc9b
Merge pull request #2685 from pixelfed/staging
Staging
2021-02-24 20:08:32 -07:00
Daniel Supernault
a1f1b6e48c
Update changelog 2021-02-24 20:07:30 -07:00
Daniel Supernault
f53bfa6fa6
Update Compose apis, prevent private accounts from posting public or unlisted scopes 2021-02-24 20:06:58 -07:00
daniel
390c083d12
Merge pull request #2682 from pixelfed/staging
Update user admin, fix pagination
2021-02-23 00:32:47 -07:00
Daniel Supernault
43201a70e6
Update user admin, fix pagination 2021-02-23 00:31:12 -07:00
daniel
46ca396360
Merge pull request #2680 from pixelfed/staging
Update user admin, remove expensive db query and add search
2021-02-23 00:06:01 -07:00
Daniel Supernault
53042d295e
Update changelog 2021-02-22 23:05:52 -07:00
Daniel Supernault
8feeadbf4e
Update user admin, remove expensive db query and add search 2021-02-22 23:03:49 -07:00
Sasi Olin
7e7d2e60dd
Fix label describing the wrong field in settings 2021-02-22 22:18:05 +01:00
daniel
2ff863fa00
Merge pull request #2666 from pixelfed/staging
Update MediaStorageService, improve head checks to fix failed jobs
2021-02-17 01:16:31 -07:00
Daniel Supernault
7b7e7cf697
Update changelog 2021-02-17 01:14:24 -07:00
Daniel Supernault
1769cdfd74
Update MediaStorageService, improve head checks to fix failed jobs 2021-02-17 01:09:39 -07:00
daniel
c2a674d2c5
Merge pull request #2665 from pixelfed/staging
Update federation pipeline, add locks
2021-02-17 00:01:03 -07:00
Daniel Supernault
850e4499f1
Update web routes 2021-02-16 23:59:10 -07:00
Daniel Supernault
acbca90c78
Update changelog 2021-02-16 23:56:54 -07:00
Daniel Supernault
ddc768871b
Update federation pipeline, add locks 2021-02-16 23:39:39 -07:00
daniel
68586d765b
Merge pull request #2664 from pixelfed/staging
Update SnowflakeTest, fixes #2661
2021-02-16 23:00:15 -07:00
Daniel Supernault
c55f14764f
Update SnowflakeTest, fixes #2661 2021-02-16 22:59:40 -07:00
daniel
32a9afc7ac
Merge pull request #2660 from pixelfed/staging
Staging
2021-02-15 21:07:51 -07:00
Daniel Supernault
2ab73e1e96
Update AdminController, update reports method 2021-02-15 21:07:18 -07:00
Daniel Supernault
a70ab1ba36
Update reports view 2021-02-15 20:38:33 -07:00
daniel
a0cfe87224
Merge pull request #2659 from pixelfed/staging
Staging
2021-02-15 20:35:16 -07:00
Daniel Supernault
0118f2c685
Update compiled assets 2021-02-15 20:33:21 -07:00
Daniel Supernault
ae23d0ad1e
Update changelog 2021-02-15 20:28:04 -07:00
Daniel Supernault
e64b4bd382
Update Timeline component, add inline reports modal 2021-02-15 20:27:20 -07:00
Daniel Supernault
3dfb8738a2
Update PostComponent, update menu 2021-02-15 20:26:33 -07:00
Daniel Supernault
4507d4520c
Update RestrictedNames 2021-02-15 20:23:44 -07:00
Daniel Supernault
5206b09627
Update ReportController, handle json reports 2021-02-15 20:23:06 -07:00
Daniel Supernault
2cf85394b3
Update components.js, update read more 2021-02-15 17:36:52 -07:00
Daniel Supernault
d4c647cb4d
Update ReportController, add new report types 2021-02-15 02:49:17 -07:00
Daniel Supernault
b1fd99644b
Update AdminController, show open reports by default 2021-02-15 02:46:58 -07:00
Daniel Supernault
52a120c0db
Update admin reports views 2021-02-15 02:44:51 -07:00
Daniel Supernault
85bd4847e4
Update api routes, remove throttle middleware 2021-02-15 02:28:35 -07:00
daniel
0d7aee0e21
Merge pull request #2658 from pixelfed/staging
Updated Status model, refactor liked and shared methods to fix cache invalidation bug
2021-02-14 22:38:48 -07:00
Daniel Supernault
1b92f9f455
Update changelog 2021-02-14 22:36:05 -07:00
Daniel Supernault
f05c3b66fc
Update Status model, refactor liked and shared methods to fix cache invalidation bug 2021-02-14 22:35:31 -07:00
daniel
2fddef80a8
Merge pull request #2651 from pixelfed/staging
Staging
2021-02-12 23:16:56 -07:00
Daniel Supernault
2598520bbe
Update tests 2021-02-12 22:50:46 -07:00
Daniel Supernault
f63f48beb6
Update changelog 2021-02-12 22:27:45 -07:00
Daniel Supernault
d1c5e9b867
Update InboxPipeline, fail earlier for invalid public keys. Fixes #2648 2021-02-12 22:25:34 -07:00
Daniel Supernault
40db9a1296
Update changelog 2021-02-12 21:44:46 -07:00
Daniel Supernault
deb6f1153f
Update FederationController, return 404 for invalid webfinger addresses. Fixes #2647 2021-02-12 21:44:06 -07:00
Christoph Stelz
f5aa89b2d2 Add fix for InstagramImport 2021-02-09 09:34:05 +01:00
daniel
4150a72b18
Merge pull request #2641 from pixelfed/staging
Fix Profile + Status embeds, remove following count and improve cache invalidation and hidden follower counts.
2021-02-09 00:45:04 -07:00
Daniel Supernault
723eb00039
Update changelog 2021-02-09 00:42:57 -07:00
Daniel Supernault
5ac9d0e8f2
Update Embeds. Fix Profile + Status embeds, remove following count and improve cache invalidation and hidden follower counts 2021-02-09 00:42:12 -07:00
daniel
12c5f74e6b
Merge pull request #2638 from pixelfed/staging
Staging
2021-02-07 00:21:08 -07:00
Daniel Supernault
d31a1dbcdf
Update compiled assets 2021-02-07 00:20:26 -07:00
Daniel Supernault
2962c389ad
Update changelog 2021-02-07 00:18:20 -07:00
Daniel Supernault
ae90eef965
Update filesystems config, add backup driver to store backups on other filesystems 2021-02-07 00:14:52 -07:00
Daniel Supernault
551365183e
Update Hashtag component, fix null infinite loading bug. Fixes #2637 2021-02-07 00:12:19 -07:00
Daniel Supernault
12ce76026f
Update ComposeModal, show filter warning for unsupported browsers 2021-02-07 00:09:28 -07:00
daniel
57ee2734ba
Merge pull request #2636 from pixelfed/staging
Staging
2021-02-06 21:24:18 -07:00
Daniel Supernault
88d801bdf0
Update compiled assets 2021-02-06 21:23:42 -07:00
Daniel Supernault
6c6115b2b0
Update changelog 2021-02-06 21:23:02 -07:00
Daniel Supernault
ceae664ce0
Update ComposeModal, prevent tagging empty users. Fixes #2633 2021-02-06 21:22:27 -07:00
daniel
2558648e24
Merge pull request #2635 from pixelfed/staging
Revert database change
2021-02-06 21:07:19 -07:00
Daniel Supernault
60554c24b0
Revert database change 2021-02-06 21:06:19 -07:00
daniel
4884875f15
Merge pull request #2634 from pixelfed/staging
Staging
2021-02-06 21:04:32 -07:00
Daniel Supernault
e462b2af13
Update compiled assets 2021-02-06 21:03:55 -07:00
Daniel Supernault
1f09b4f5c5
Revert ComposeModal 2021-02-06 21:00:42 -07:00
daniel
6c9e9b15d6
Merge pull request #2632 from pixelfed/trwnh-patch-1
change default client to phpredis
2021-02-06 20:14:20 -07:00
trwnh
aa99784fed
change default client to phpredis 2021-02-06 20:53:56 -06:00
daniel
2d28257054
Merge pull request #2630 from pixelfed/trwnh-patch-1
increase default upload limit from 1M to 15M
2021-02-06 18:33:08 -07:00
trwnh
02ad300443
increase default upload limit from 1M to 15M 2021-02-06 19:30:58 -06:00
daniel
e9dcc3c72a
Merge pull request #2623 from pixelfed/staging
Update web routes
2021-02-05 21:31:55 -07:00
Daniel Supernault
946c909783
Update web routes 2021-02-05 21:31:23 -07:00
daniel
9cd9b68664
Merge pull request #2622 from pixelfed/staging
Staging
2021-02-05 21:26:03 -07:00
Daniel Supernault
e6a897bf3f
Update compiled assets 2021-02-05 21:25:25 -07:00
Daniel Supernault
832dac45c5
Update changelog 2021-02-05 21:22:25 -07:00
Daniel Supernault
a9e98965ab
Update components, improve content warnings 2021-02-05 21:21:41 -07:00
Daniel Supernault
a1059a6e12
Update DiscoverComponent, allow unathenicated if enabled 2021-02-05 21:20:19 -07:00
Daniel Supernault
e6e76e809d
Update ComposeModal, add processing step disabled by default 2021-02-05 21:19:12 -07:00
Daniel Supernault
33b625f508
Update ComposeController, add mediaProcessingCheck method 2021-02-05 21:15:13 -07:00
Daniel Supernault
6bee5072d0
Update pixelfed config, add media_fast_process setting 2021-02-05 21:13:52 -07:00
daniel
ec5646d098
Merge pull request #2621 from pixelfed/staging
Staging
2021-02-05 21:12:12 -07:00
Daniel Supernault
18ee2def92
Update changelog 2021-02-05 21:10:25 -07:00
Daniel Supernault
1404ac6e6f
Update Discover, allow public discover access 2021-02-05 21:09:03 -07:00
Daniel Supernault
9fd90e174b
Update StatusDeletePipeline, use StorageMediaService for media deletes 2021-02-05 21:05:58 -07:00
Daniel Supernault
ab5469ff70
Update ComposeController, use MediaStorageService for media deletes 2021-02-05 21:04:34 -07:00
Daniel Supernault
37dbb3de29
Update MediaStorageService, dispatch deletes to MediaDeletePipeline 2021-02-05 21:00:29 -07:00
daniel
83aecc5595
Merge pull request #2614 from pixelfed/staging
Update webfinger util, fail on invalid webfinger url. Fixes #2613
2021-02-03 20:59:53 -07:00
Daniel Supernault
0cef5aec26
Update changelog 2021-02-03 20:57:31 -07:00
Daniel Supernault
2d11317ceb
Update webfinger util, fail on invalid webfinger url 2021-02-03 20:55:49 -07:00
Daniel Supernault
aad07e2c83
Update changelog 2021-02-01 00:48:36 -07:00
Daniel Supernault
b299da9311
Update AccountService, cache object and observe changes 2021-02-01 00:47:54 -07:00
daniel
7290445f78
Merge pull request #2604 from pixelfed/staging
Add new api endpoints
2021-01-31 13:50:00 -07:00
Daniel Supernault
61c71e8905
Update changelog 2021-01-31 13:45:58 -07:00
Daniel Supernault
1fb7e2b2c9
Update AccountController, add mutes and blocks endpoint to pixelfed api 2021-01-31 13:45:22 -07:00
daniel
59edf497bf
Merge pull request #2603 from pixelfed/staging
Staging
2021-01-31 01:01:41 -07:00
Daniel Supernault
fc0afcb2f8
Update compiled assets 2021-01-31 01:01:11 -07:00
Daniel Supernault
6af2bea5e0
Update DiscoverComponent 2021-01-31 00:58:24 -07:00
Daniel Supernault
2c661b815c
Update changelog 2021-01-31 00:51:09 -07:00
Daniel Supernault
c56a299cfb
Update compiled assets 2021-01-31 00:50:51 -07:00
Daniel Supernault
348692476c
Update DiscoverComponent, add spinner loaders and remove deprecated sections 2021-01-31 00:49:40 -07:00
Daniel Supernault
b16e9452bc
Update DiscoverController, deprecate unused endpoints 2021-01-30 23:13:54 -07:00
daniel
4c2661b8dd
Merge pull request #2602 from pixelfed/staging
Staging
2021-01-30 22:20:34 -07:00
Daniel Supernault
c34a8f4556
Update changelog 2021-01-30 22:16:51 -07:00
Daniel Supernault
eadf0c3b6c
Update nav partial 2021-01-30 22:15:54 -07:00
Daniel Supernault
1324605389
Update footer 2021-01-30 22:11:12 -07:00
Daniel Supernault
7075b7f83e
Update compiled assets 2021-01-30 21:58:37 -07:00
Daniel Supernault
a8ebdd2e39
Update DiscoverComponent, add blurhash and like/comment counts 2021-01-30 21:56:50 -07:00
Daniel Supernault
9862a85599
Update InternalApiController, update discoverPosts method to improve performance 2021-01-30 21:39:31 -07:00
Daniel Supernault
1ac60173af
Update AccountLog model, add fillable attribute 2021-01-30 19:56:31 -07:00
Daniel Supernault
090b6d0336
Update SeasonalController 2021-01-30 19:56:10 -07:00
Daniel Supernault
2281727ac0
Update nav partial 2021-01-30 19:45:02 -07:00
daniel
ee62997168
Merge pull request #2601 from pixelfed/staging
Update AP helpers, fix statusFetch 404s
2021-01-30 18:43:53 -07:00
Daniel Supernault
b5ecb2ebc8
Update changelog 2021-01-30 18:43:40 -07:00
Daniel Supernault
3419379aa8
Update AP helpers, fix statusFetch 404s 2021-01-30 18:41:51 -07:00
daniel
6840f81c04
Merge pull request #2600 from pixelfed/staging
Staging
2021-01-30 18:17:48 -07:00
Daniel Supernault
69d7f7e452
Update compiled assets 2021-01-30 18:16:40 -07:00
Daniel Supernault
55248ef092
Update changelog 2021-01-30 18:16:26 -07:00
Daniel Supernault
7768e84485
Update Timeline.vue, fix hashtag status previews 2021-01-30 18:15:34 -07:00
Daniel Supernault
240e36ed45
Update AP helpers 2021-01-30 18:14:04 -07:00
daniel
8757476aff
Merge pull request #2599 from pixelfed/staging
Staging
2021-01-30 17:39:04 -07:00
Daniel Supernault
d90b085906
Update changelog 2021-01-30 17:34:04 -07:00
Daniel Supernault
3f14a4c412
Update MediaTransformers, add default blurhash attribute 2021-01-30 17:31:07 -07:00
Daniel Supernault
896452c74c
Update VideoThumbnail job, generate blurhash for videos 2021-01-30 17:30:04 -07:00
Daniel Supernault
38a37c15af
Update Blurhash util, add default hash for invalid media 2021-01-30 17:28:35 -07:00
Daniel Supernault
8cfe7d79e1
Update compiled assets 2021-01-30 17:16:56 -07:00
Daniel Supernault
39e389dd4e
Update status square previews, add blurhash and improved content warnings 2021-01-30 17:15:49 -07:00
Daniel Supernault
9e0fd36ba7
Update Status tranformers, add version attribute 2021-01-30 16:39:42 -07:00
Daniel Supernault
899bbeba5f
Update StatusHashtagTransformer, add blurhash attribute 2021-01-30 16:38:43 -07:00
Daniel Supernault
3f772ff864
Update StatusDelete pipeline, call StatusService::del() to remove status from cache 2021-01-30 16:37:29 -07:00
Daniel Supernault
069f20ff77
Update LikePipeline, add StatusService del() method 2021-01-30 16:36:58 -07:00
Daniel Supernault
2eea04097a
Update DiscoverController, change api schema 2021-01-30 16:35:52 -07:00
Daniel Supernault
789ed4b498
Update ComposeController, use placeholder image for video media. Fixes #2595 2021-01-30 16:34:44 -07:00
Daniel Supernault
eab4370c84
Update Like, Status and Comment controllers to add StatusService del() method to update counts 2021-01-30 16:25:50 -07:00
Daniel Supernault
05b9445c8f
Update ApiV1Controller, add StatusService del calls to update likes_count, reblogs_count and reply_count 2021-01-30 16:15:56 -07:00
Daniel Supernault
aa4c718d79
Update StatusHashtagService, remove deprecated methods 2021-01-30 16:09:17 -07:00
Daniel Supernault
0355b567dd
Update StatusHashtagService, use StatusService for statuses 2021-01-30 16:07:12 -07:00
Daniel Supernault
6e44ae0b64
Update StatusService, add ttl of 7 days 2021-01-30 16:05:18 -07:00
daniel
c898dc5408
Merge pull request #2587 from dunn/docker-logging
.env.docker: log to stderr
2021-01-30 09:50:28 -07:00
daniel
25fd23a81b
Merge pull request #2597 from pixelfed/staging
Staging
2021-01-30 09:48:09 -07:00
Daniel Supernault
7d6e42f246
Update changelog 2021-01-30 09:47:34 -07:00
Daniel Supernault
4f40f6f5a1
Update RemotAvatarFetch, only dispatch jobs if cloud storage is enabled 2021-01-30 09:47:02 -07:00
Daniel Supernault
5a116c7ae0
Update changelog 2021-01-30 09:37:02 -07:00
Daniel Supernault
6edaf94099
Update AdminController, fix variable name in updateSpam method 2021-01-30 09:36:09 -07:00
daniel
aebd65c6a3
Merge pull request #2594 from pixelfed/staging
Staging
2021-01-29 19:48:24 -07:00
Daniel Supernault
b7fcf8a504
Update StatusLabelService 2021-01-29 19:46:55 -07:00
Daniel Supernault
cc93b3e3a5
Update package.json, fixes #2593 2021-01-29 19:46:24 -07:00
daniel
ed94041a19
Merge pull request #2592 from pixelfed/staging
Update StatusTransformer, add blurhash
2021-01-29 01:39:11 -07:00
Daniel Supernault
23db9de870
Update StatusTransformer, add blurhash 2021-01-29 01:38:40 -07:00
daniel
5d85a7977b
Merge pull request #2591 from pixelfed/staging
Staging
2021-01-28 23:09:19 -07:00
Daniel Supernault
30cf865154
Update compiled assets 2021-01-28 23:08:26 -07:00
Daniel Supernault
54abfef4f7
Update Timeline.vue, add border-top to status label 2021-01-28 23:07:58 -07:00
Daniel Supernault
b43bb1dd0d
Update web routes 2021-01-28 22:38:30 -07:00
daniel
4deec1f6c1
Merge pull request #2571 from pixelfed/staging
v0.10.10
2021-01-28 21:36:08 -07:00
Daniel Supernault
98cdd52e99
Bump version to v0.10.10 2021-01-28 21:34:42 -07:00
Daniel Supernault
d0e7f13ea2
Bump changelog 2021-01-28 21:33:56 -07:00
Daniel Supernault
2841a42d72
Update changelog 2021-01-28 21:32:16 -07:00
Daniel Supernault
fa0df4004c
Update AP Helpers 2021-01-28 21:26:45 -07:00
Daniel Supernault
7d376c64ec
Update SiteController, only allow redirects for logged in users 2021-01-28 21:25:43 -07:00
Daniel Supernault
10b178c8ee
Update SiteController, use url validator in redirect endpoint 2021-01-28 21:23:15 -07:00
Daniel Supernault
c45ea96eb3
Update compiled assets 2021-01-28 20:11:15 -07:00
Daniel Supernault
f38d2a09a4
Update components 2021-01-28 20:08:06 -07:00
Daniel Supernault
4abfe76a49
Update StatusLabelService, change config key 2021-01-28 18:42:15 -07:00
Daniel Supernault
abe9cb3db0
Update site config, add labels to config 2021-01-28 18:40:13 -07:00
Daniel Supernault
ab888b2e70
Add StatusLabelService 2021-01-27 22:25:14 -07:00
Alexandra Dunn
927ef3b057 docker-compose: store default db passwords in .env.docker
and have redis use a password as well
2021-01-27 11:58:24 -08:00
Alexandra Dunn
3bf2366d82 .env.docker: log to stderr 2021-01-27 11:57:20 -08:00
Daniel Supernault
10ca7ac052
Update static assets 2021-01-26 22:40:01 -07:00
Daniel Supernault
52bb90fd40
Update compiled assets 2021-01-26 22:02:37 -07:00
Daniel Supernault
674701c1df
Update web routes 2021-01-26 21:49:56 -07:00
Daniel Supernault
df980d0092
Update composer 2021-01-26 21:16:44 -07:00
Daniel Supernault
184854b0de
Update compiled assets 2021-01-26 21:11:14 -07:00
Daniel Supernault
cc84125ba2
Update Timeline, prevent nextTick() when reloading same comment modal. Fixes #2584 2021-01-26 20:33:09 -07:00
Daniel Supernault
de8828e88a
Update AP Helpers, add blurhash and RemoteAvatarFetch 2021-01-25 22:24:24 -07:00
Daniel Supernault
cc2d4bf8d8
Update ComposeController, update media version 2021-01-25 22:22:01 -07:00
Daniel Supernault
4c148055cf
Update AvatarPipeline, add remote avatar fetch 2021-01-25 22:03:27 -07:00
Daniel Supernault
94a9f685b5
Update MediaStorageService, add avatar method 2021-01-25 21:54:30 -07:00
Daniel Supernault
654b08d382
Update ActivityPubFetchService, add url validation 2021-01-25 21:44:07 -07:00
Daniel Supernault
ea8e426174
Update Profile model, use cdn_url for avatars 2021-01-25 21:39:10 -07:00
Daniel Supernault
9eafc31e6f
Update AvatarObserver, add logic to delete avatars stored in S3 2021-01-25 21:37:44 -07:00
Daniel Supernault
f7e72d7c62
Update avatar model 2021-01-25 21:30:37 -07:00
Daniel Supernault
9c5e6a7172
Update migration 2021-01-25 21:06:25 -07:00
Daniel Supernault
1031311f06
Update RestrictedNames, add additional static assets 2021-01-25 17:48:03 -07:00
Daniel Supernault
77f21b4b33
Update AP helpers, only run MediaStoragePipeline if using cloud storage 2021-01-24 21:13:31 -07:00
Daniel Supernault
258b2729d3
Update MediaTransformer, remove cache busting 2021-01-24 21:11:52 -07:00
Daniel Supernault
ce6ab80dba
Update MediaStorageService, clear transformer cache after storing media 2021-01-24 21:07:28 -07:00
Daniel Supernault
f930c4bda2
Update StatusDelete pipeline, fix object storage thumbnail deletion 2021-01-24 19:08:55 -07:00
Daniel Supernault
98c44f7bdb
Update VideoThumbnail, add MediaStoragePipeline 2021-01-24 18:58:38 -07:00
Daniel Supernault
9051c1f998
Add migration 2021-01-24 18:24:25 -07:00
Daniel Supernault
889c3d8758
Update AvatarController, remove deprecated thumb_path 2021-01-24 18:22:13 -07:00
Daniel Supernault
1e9958f521
Update changelog 2021-01-24 15:48:00 -07:00
Daniel Supernault
7736bfaa8a
Update compiled assets 2021-01-24 15:47:52 -07:00
Daniel Supernault
b2b8c9f99c
Update UserCreate command, closes #2581 2021-01-24 15:43:37 -07:00
Daniel Supernault
e8cc66dce7
Update components, fix url rewriter. Closes #2538 2021-01-24 15:32:14 -07:00
Daniel Supernault
80b911c4d2
Update changelog 2021-01-24 14:28:25 -07:00
Daniel Supernault
ed6877df7a
Update cache config, use phpredis by default 2021-01-24 14:13:56 -07:00
Daniel Supernault
febe9e1b6d
Add ComposeController 2021-01-24 14:11:58 -07:00
Daniel Supernault
2f639fb5c7
Update compiled assets 2021-01-24 14:11:10 -07:00
Daniel Supernault
2a890c6618
Update web routes 2021-01-24 14:05:49 -07:00
Daniel Supernault
9683e846e9
Update blade views 2021-01-24 14:03:55 -07:00
Daniel Supernault
05b7e1395f
Update Timeline component 2021-01-24 13:39:56 -07:00
Daniel Supernault
c1118956c3
Update RemoteProfile component, change thumbnail url 2021-01-24 13:36:26 -07:00
Daniel Supernault
1c608440a5
Update package lock 2021-01-24 13:35:28 -07:00
Daniel Supernault
01a1ffd64a
Update AP Helpers, use MediaStoragePipeline 2021-01-24 13:32:20 -07:00
Daniel Supernault
be6d12fcb6
Update MediaPipeline, handle cloud object storage 2021-01-24 13:30:31 -07:00
Daniel Supernault
2538673a7b
Update StatusController, unescape slashes in json response 2021-01-24 13:22:10 -07:00
Daniel Supernault
6d078377f1
Update AdminController 2021-01-23 23:12:05 -07:00
Daniel Supernault
9cd4bd74a5
Update backup config, prevents gateway timeouts for large databases using mysql 2021-01-22 17:53:06 -07:00
Daniel Supernault
0eabbfdd07
Update storage, add remote media cache directory 2021-01-19 23:57:30 -07:00
Daniel Supernault
2284d85f9c
Update VideoThumbnail job, remove outdated cloud storage logic 2021-01-19 19:42:31 -07:00
Daniel Supernault
4a70e18596
Update Media model, fix url methods 2021-01-19 19:40:41 -07:00
Daniel Supernault
6b411d23b5
Update changelog 2021-01-19 19:39:36 -07:00
Daniel Supernault
f8cbe1e42c
Update MediaTransformers, include meta attribute with focus and dimensions 2021-01-19 19:39:04 -07:00
Daniel Supernault
40bd64aae7
Update Image media util, store dimensions of media not thumbnail 2021-01-19 19:36:05 -07:00
Daniel Supernault
cf40526ef9
Update mobile apis, add blurhash 2021-01-18 18:25:03 -07:00
Daniel Supernault
4415af1bf0
Update api controllers, deprecate old endpoints 2021-01-17 23:43:23 -07:00
Daniel Supernault
8132db74e5
Update MediaController, remove deprecated endpoint 2021-01-17 23:37:10 -07:00
Daniel Supernault
57fa889d16
Update Media model, add cdn support to url and thumbnailUrl methods 2021-01-17 22:45:40 -07:00
Daniel Supernault
a98b65bf5c
Update DiscoverController 2021-01-17 22:02:27 -07:00
Daniel Supernault
234f72f3aa
Update ImageOptimizePipeline, add skip_optimize and MediaStorageService support 2021-01-17 21:59:34 -07:00
Daniel Supernault
4b1a0fd750
Update StatusDelete job, handle cloud storage media deletes 2021-01-17 21:51:25 -07:00
Daniel Supernault
aac4430970
Update MediaPathService, add story method 2021-01-17 21:46:40 -07:00
Daniel Supernault
0cfc12c5dd
Update StatusController, add view method 2021-01-17 21:38:34 -07:00
Daniel Supernault
5e13643246
Update MediaTagController 2021-01-17 21:27:55 -07:00
Daniel Supernault
8fc741c68d
Update web routes 2021-01-17 21:25:41 -07:00
Daniel Supernault
caafef580c
Update ComposeModal, use new routes 2021-01-17 20:25:19 -07:00
Daniel Supernault
a0a15730db
Update help template 2021-01-17 19:55:04 -07:00
Daniel Supernault
8474d32114
Add instance actor help page 2021-01-17 19:53:01 -07:00
Daniel Supernault
fcebf5960f
Update changelog 2021-01-17 19:51:21 -07:00
Daniel Supernault
f32072a396
Add Year in Review feature (mysql only) 2021-01-17 19:50:35 -07:00
Daniel Supernault
b00e2b0868
Update EmailService 2021-01-17 15:33:56 -07:00
Daniel Supernault
5b0b14fc42
Update StoryItemTransformer, increase story duration from 5 seconds to 10 seconds 2021-01-17 15:27:26 -07:00
Daniel Supernault
c91891b457
Update InstanceActor model 2021-01-17 14:03:32 -07:00
Daniel Supernault
1450f54f65
Update api routes 2021-01-17 14:00:26 -07:00
Daniel Supernault
07f419d5cb
Update changelog 2021-01-17 12:51:37 -07:00
Daniel Supernault
3ee1215a4a
Add signed GET for secure mode compatibility 2021-01-17 12:51:07 -07:00
Daniel Supernault
b29b845533
Update components 2021-01-17 03:07:08 -07:00
679 changed files with 142908 additions and 44723 deletions

View file

@ -7,7 +7,7 @@ jobs:
build: build:
docker: docker:
# Specify the version you desire here # Specify the version you desire here
- image: circleci/php:7.3-cli-stretch-node - image: cimg/php:7.4.26
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images
@ -22,7 +22,6 @@ jobs:
- checkout - checkout
- run: sudo apt update && sudo apt install zlib1g-dev libsqlite3-dev - run: sudo apt update && sudo apt install zlib1g-dev libsqlite3-dev
- run: sudo -E docker-php-ext-install bcmath pcntl zip
# Download and cache dependencies # Download and cache dependencies

View file

@ -56,11 +56,16 @@ MAIL_ENCRYPTION=null
## Databases (MySQL) ## Databases (MySQL)
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_DATABASE=pixelfed_prod
DB_HOST=db
DB_PASSWORD=pixelfed_db_pass
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=pixelfed
DB_USERNAME=pixelfed DB_USERNAME=pixelfed
DB_PASSWORD=pixelfed # pass the same values to the db itself
MYSQL_DATABASE=pixelfed_prod
MYSQL_PASSWORD=pixelfed_db_pass
MYSQL_RANDOM_ROOT_PASSWORD=true
MYSQL_USER=pixelfed
## Databases (Postgres) ## Databases (Postgres)
#DB_CONNECTION=pgsql #DB_CONNECTION=pgsql
@ -74,7 +79,7 @@ DB_PASSWORD=pixelfed
REDIS_CLIENT=phpredis REDIS_CLIENT=phpredis
REDIS_SCHEME=tcp REDIS_SCHEME=tcp
REDIS_HOST=redis REDIS_HOST=redis
REDIS_PASSWORD=null REDIS_PASSWORD=redis_password
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_DATABASE=0 REDIS_DATABASE=0
@ -115,7 +120,7 @@ PF_COSTAR_ENABLED=false
MEDIA_EXIF_DATABASE=false MEDIA_EXIF_DATABASE=false
## Logging ## Logging
LOG_CHANNEL=stack LOG_CHANNEL=stderr
## Image ## Image
IMAGE_DRIVER=imagick IMAGE_DRIVER=imagick

View file

@ -1,16 +1,365 @@
# Release Notes # Release Notes
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.9...dev) ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.2...dev)
### Added
- Custom Emoji ([#3166](https://github.com/pixelfed/pixelfed/pull/3166))
### Metro 2.0 UI
- Added Hovercards ([16ced7b4](https://github.com/pixelfed/pixelfed/commit/16ced7b4))
- Fix word-break on statuses ([16ced7b4](https://github.com/pixelfed/pixelfed/commit/16ced7b4))
- Add pronouns to hovercards ([33f863e8](https://github.com/pixelfed/pixelfed/commit/33f863e8))
- Improved onboarding ([042c5b6c](https://github.com/pixelfed/pixelfed/commit/042c5b6c))
### Updated
- Updated MediaStorageService, fix remote avatar bug. ([1c20d696](https://github.com/pixelfed/pixelfed/commit/1c20d696))
- Updated WebfingerService. Fixes #3167. ([aff74566](https://github.com/pixelfed/pixelfed/commit/aff74566))
- Updated ComposeModal, add max file size and allowed mime types. Fixes #3162. ([879281cc](https://github.com/pixelfed/pixelfed/commit/879281cc))
- Updated profile embeds, fix NaN bug and improve performance. ([3bd211d7](https://github.com/pixelfed/pixelfed/commit/3bd211d7))
- Updated ApiV1Controller, improve follow count cache invalidation. ([4b6effb9](https://github.com/pixelfed/pixelfed/commit/4b6effb9))
- Updated web routes, fix atom feeds for account usernames containing a dot. ([8c54ab57](https://github.com/pixelfed/pixelfed/commit/8c54ab57))
- Updated atom feeds, include media alt text. Fixes #3184. ([5d9b6863](https://github.com/pixelfed/pixelfed/commit/5d9b6863))
- Updated ApiV1Controller, add custom_emoji endpoint. ([16e72518](https://github.com/pixelfed/pixelfed/commit/16e72518))
- Updated InternalApiController, redirect remote post and profiles to Metro 2.0. ([3c35158e](https://github.com/pixelfed/pixelfed/commit/3c35158e))
- Updated BaseApiController, improve favourites endpoint. ([f063cb01](https://github.com/pixelfed/pixelfed/commit/f063cb01))
- Updated ApiV1Controller, invalidate status reply cache on new reply. ([3c261bbf](https://github.com/pixelfed/pixelfed/commit/3c261bbf))
- Updated PublicApiController, add bookmark state to timeline endpoints. ([c0b1e042](https://github.com/pixelfed/pixelfed/commit/c0b1e042))
- Updated ApiV1Controller, fix private status replies returning 404. ([73226360](https://github.com/pixelfed/pixelfed/commit/73226360))
- Updated StatusService, use BookmarkService for bookmarked state. ([a7d71551](https://github.com/pixelfed/pixelfed/commit/a7d71551))
- Updated Apis, added ReblogService to improve reblogged state for api entities ([6cfd6be5](https://github.com/pixelfed/pixelfed/commit/6cfd6be5))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.2 (2022-01-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.1...v0.11.2)
### Breaking
- Dropped support for PHP 7.3 [#3041](https://github.com/pixelfed/pixelfed/pull/3041)
### Metro 2.0 UI
- Added UI Settings modal and fixed height media previews setting ([f2467e71](https://github.com/pixelfed/pixelfed/commit/f2467e71))
- Set max-width of 1440px for larger screens ([af68872a](https://github.com/pixelfed/pixelfed/commit/af68872a))
- Add link to sidebar profile card ([85964510](https://github.com/pixelfed/pixelfed/commit/85964510))
- Improved search bar, now resolves (and imports) remote accounts and posts, including webfinger addresses ([c8a667f2](https://github.com/pixelfed/pixelfed/commit/c8a667f2))
- Added user facing changelog at `/i/web/whats-new` ([e61dc66a](https://github.com/pixelfed/pixelfed/commit/e61dc66a))
### Configuration
- Enable network timeline by default ([b95aec12](https://github.com/pixelfed/pixelfed/commit/b95aec12))
### Postgres Compatibility
- Fix Story recent endpoint on postgres instances ([ddf41dc3](https://github.com/pixelfed/pixelfed/commit/ddf41dc3))
- Fix Direct Message conversations endpoint on postgres instances ([fcabc9be](https://github.com/pixelfed/pixelfed/commit/fcabc9be))
### Added
- Manual email verification requests. ([bc659387](https://github.com/pixelfed/pixelfed/commit/bc659387))
- Added StatusMentionService, fixes #3026. ([e5387d67](https://github.com/pixelfed/pixelfed/commit/e5387d67))
- Cloud Backups, a command to store backups on S3 or compatible filesystems. [#3037](https://github.com/pixelfed/pixelfed/pull/3037) ([3515a98e](https://github.com/pixelfed/pixelfed/commit/3515a98e))
- Web UI Localizations + Crowdin integration. ([f7d9b40b](https://github.com/pixelfed/pixelfed/commit/f7d9b40b)) ([7ff120c9](https://github.com/pixelfed/pixelfed/commit/7ff120c9))
- Store remote avatars locally if S3 not enabled. ([b4bd0400](https://github.com/pixelfed/pixelfed/commit/b4bd0400))
### Updated
- Updated NotificationService, fix 500 bug. ([4a609dc3](https://github.com/pixelfed/pixelfed/commit/4a609dc3))
- Updated HttpSignatures, update instance actor headers. Fixes #2935. ([a900de21](https://github.com/pixelfed/pixelfed/commit/a900de21))
- Updated NoteTransformer, fix tag array. ([7b3e672d](https://github.com/pixelfed/pixelfed/commit/7b3e672d))
- Updated video presenters, add playsinline attribute to video tags. ([0299aa5b](https://github.com/pixelfed/pixelfed/commit/0299aa5b))
- Updated RemotePost, RemoteProfile components, add fallback avatars. ([754151dc](https://github.com/pixelfed/pixelfed/commit/754151dc))
- Updated FederationController, move well-known to api middleware and cache webfinger lookups. ([4505d1f0](https://github.com/pixelfed/pixelfed/commit/4505d1f0))
- Updated InstanceActorController, improve json seralization by not escaping slashes. ([0a8eb81b](https://github.com/pixelfed/pixelfed/commit/0a8eb81b))
- Refactor following & relationship logic. Replace FollowerObserver with FollowerService and added RelationshipService to cache results. Removed NotificationTransformer includes and replaced with cached services to improve performance and reduce database queries. ([80d9b939](https://github.com/pixelfed/pixelfed/commit/80d9b939))
- Updated PublicApiController, use AccountService in accountStatuses method. ([bef959f4](https://github.com/pixelfed/pixelfed/commit/bef959f4))
- Updated auth config, add throttle limit to password resets. ([2609c86a](https://github.com/pixelfed/pixelfed/commit/2609c86a))
- Updated StatusCard component, add relationship state button. ([0436b124](https://github.com/pixelfed/pixelfed/commit/0436b124))
- Updated Timeline component, cascade relationship state change. ([f4bd5672](https://github.com/pixelfed/pixelfed/commit/f4bd5672))
- Updated Activity component, only show context button for actionable activities. ([7886fd59](https://github.com/pixelfed/pixelfed/commit/7886fd59))
- Updated Autospam service, use silent classification for better user experience. ([f0d4c172](https://github.com/pixelfed/pixelfed/commit/f0d4c172))
- Updated Profile component, improve error messages when block/mute limit reached. ([02237845](https://github.com/pixelfed/pixelfed/commit/02237845))
- Updated Activity component, fix missing types. ([5167c68d](https://github.com/pixelfed/pixelfed/commit/5167c68d))
- Updated Timeline component, apply block/mute filters client side for local and network timelines. ([be194b8a](https://github.com/pixelfed/pixelfed/commit/be194b8a))
- Updated public timeline api, use cached sorted set and client side block/mute filtering. ([37abcf38](https://github.com/pixelfed/pixelfed/commit/37abcf38))
- Updated public timeline api, add experimental cache. ([192553ff](https://github.com/pixelfed/pixelfed/commit/192553ff))
- Updated dark mode styles, fix black box on stories. Closes #2982. ([3169f68e](https://github.com/pixelfed/pixelfed/commit/3169f68e))
- Updated verify_credentials api endpoint to improve performance. ([7df3540b](https://github.com/pixelfed/pixelfed/commit/7df3540b))
- Updated Localization util, filter out .DS_Store. ([0107e8fd](https://github.com/pixelfed/pixelfed/commit/0107e8fd))
- Updated PublicApiController, fix private account statuses api. Closes #2995. ([aa2dd26c](https://github.com/pixelfed/pixelfed/commit/aa2dd26c))
- Updated Status model, use AccountService to generate urls instead of loading profile relation. ([2ae527c0](https://github.com/pixelfed/pixelfed/commit/2ae527c0))
- Updated Autospam service, add mark all as read and mark all as not spam options and filter active, spam and not spam reports. ([ae8c7517](https://github.com/pixelfed/pixelfed/commit/ae8c7517))
- Updated UserInviteController, fixes #3017. ([b8e9056e](https://github.com/pixelfed/pixelfed/commit/b8e9056e))
- Updated AccountService, add dynamic user settings methods. ([2aa73c1f](https://github.com/pixelfed/pixelfed/commit/2aa73c1f))
- Updated MediaStorageService, improve header parsing. ([9d9e9ce7](https://github.com/pixelfed/pixelfed/commit/9d9e9ce7))
- Updated SearchApiV2Service, improve performance and include hashtag post counts when applicable ([fbaed93e](https://github.com/pixelfed/pixelfed/commit/fbaed93e))
- Updated AccountTransformer, add note_text and location fields. ([98f76abb](https://github.com/pixelfed/pixelfed/commit/98f76abb))
- Updated UserSetting model, cast compose_settings and other as json. ([03420278](https://github.com/pixelfed/pixelfed/commit/03420278))
- Updated ApiV1Controller, improve settings and add discoverPosts endpoint. ([079804e6](https://github.com/pixelfed/pixelfed/commit/079804e6))
- Updated LikePipeline jobs, fix likes_count calculation. ([fe64e187](https://github.com/pixelfed/pixelfed/commit/fe64e187))
- Updated InternalApiController, prevent moderation actions against admin accounts. ([945a7e49](https://github.com/pixelfed/pixelfed/commit/945a7e49))
- Updated CommentPipeline, move reply_count calculation to comment pipeline job and improve count calculation. ([b6b0837f](https://github.com/pixelfed/pixelfed/commit/b6b0837f))
- Updated ApiV1Controller, improve statusesById perf and dispatch CommentPipeline job when applicable. ([466286af](https://github.com/pixelfed/pixelfed/commit/466286af))
- Updated MediaService, return empty array if cant find status. ([c2910e5d](https://github.com/pixelfed/pixelfed/commit/c2910e5d))
- Updated StatusService, improve cache invalidation. ([83b48b56](https://github.com/pixelfed/pixelfed/commit/83b48b56))
- Updated Hashtag component, fix spinner. ([fefbc44a](https://github.com/pixelfed/pixelfed/commit/fefbc44a))
- Updated NotificationCard, update api endpoint and add group notification types. ([e09a14d8](https://github.com/pixelfed/pixelfed/commit/e09a14d8))
- Updated ContextMenu component, fix account url paths. ([01ca1edd](https://github.com/pixelfed/pixelfed/commit/01ca1edd))
- Updated PollCard component, add showBorder prop. ([0c8fffbd](https://github.com/pixelfed/pixelfed/commit/0c8fffbd))
- Updated PhotoPresenter component, add lightbox toggle. ([0cc1365f](https://github.com/pixelfed/pixelfed/commit/0cc1365f))
- Updated console kernel, add db session garbage collector that runs twice daily. ([03b0a62a](https://github.com/pixelfed/pixelfed/commit/03b0a62a))
- Updated ComposeController, refactor compose_settings. ([edc2958b](https://github.com/pixelfed/pixelfed/commit/edc2958b))
- Updated StatusEntityLexer, prevent boosts and replies from being added to PublicTimelineService. ([32707372](https://github.com/pixelfed/pixelfed/commit/32707372))
- Updated SpaController, persist web language changes. ([7bc684e5](https://github.com/pixelfed/pixelfed/commit/7bc684e5))
- Updated LoginController, bump decayMinutes from 1 to 60. ([6bf92bed](https://github.com/pixelfed/pixelfed/commit/6bf92bed))
- Updated SPA, rewrite autolink urls to SPA when applicable. ([0837b410](https://github.com/pixelfed/pixelfed/commit/0837b410))
- Updated site config, increase ttl and enable SPA by default. ([469d49d8](https://github.com/pixelfed/pixelfed/commit/469d49d8))
- Updated Webfinger, fixes #3050. ([ff7ee3bd](https://github.com/pixelfed/pixelfed/commit/ff7ee3bd))
- Updated status api, autolink caption before returning response. ([b00a453b](https://github.com/pixelfed/pixelfed/commit/b00a453b))
- Updated Timeline, add new ui promo in timelines that can be hidden using localstorage. ([e13959ae](https://github.com/pixelfed/pixelfed/commit/e13959ae))
- Updated FederationController, increase webfinger cache ttl from 12 hours to 14 days. ([745c3580](https://github.com/pixelfed/pixelfed/commit/745c3580))
- Updated DiscoverController, add yearly option and increase limit from 15 to 30 posts. ([10b6058c](https://github.com/pixelfed/pixelfed/commit/10b6058c))
- Updated RemoteAvatarFetch job, fixed bug preventing new avatars from being stored. ([92bc2845](https://github.com/pixelfed/pixelfed/commit/92bc2845))
- Updated AccountService, fix json casting. ([e5f8f344](https://github.com/pixelfed/pixelfed/commit/e5f8f344))
- Updated ApiV1Controller, fix illegal operator bug by setting default min_id. ([415826f2](https://github.com/pixelfed/pixelfed/commit/415826f2))
- Updated StatusService, add getMastodon method for mastoapi compatibility. ([36a129fe](https://github.com/pixelfed/pixelfed/commit/36a129fe))
- Updated PublicApiController, fix accountStatuses pagination operator. ([85fc9dd0](https://github.com/pixelfed/pixelfed/commit/85fc9dd0))
- Updated PublicApiController, enforce only_media on accountStatuses method. Fixes #3105. ([861a2d36](https://github.com/pixelfed/pixelfed/commit/861a2d36))
- Updated ApiV1Controller, add mastoapi strict mode. ([46485426](https://github.com/pixelfed/pixelfed/commit/46485426))
- Updated AccountController, refresh RelationshipService on mute/block. ([6f1b0245](https://github.com/pixelfed/pixelfed/commit/6f1b0245))
- Updated ApiV1Controller, fix version on instance endpoint. ([a6261221](https://github.com/pixelfed/pixelfed/commit/a6261221))
- Updated components, fix api endpoints. Fixes #3138. ([e724633e](https://github.com/pixelfed/pixelfed/commit/e724633e))
- Updated ApiV1Controller, fix public timeline endpoint. ([80c7def3](https://github.com/pixelfed/pixelfed/commit/80c7def3))
- Updated PublicApiController, fix public timeline endpoint. ([dcb7ba9c](https://github.com/pixelfed/pixelfed/commit/dcb7ba9c))
- Updated ApiV1Controller, fix home timeline entities. ([6fc0dcb3](https://github.com/pixelfed/pixelfed/commit/6fc0dcb3))
- Updated ApiV1Controller, fix favourites endpoints ([d6d99385](https://github.com/pixelfed/pixelfed/commit/d6d99385))
- Updated ApiV1Controller, fix reblogs endpoints ([de42d84c](https://github.com/pixelfed/pixelfed/commit/de42d84c))
- Updated SearchApiV2Service, resolve remote queries. ([c8a667f2](https://github.com/pixelfed/pixelfed/commit/c8a667f2))
## [v0.11.1 (2021-09-07)](https://github.com/pixelfed/pixelfed/compare/v0.11.0...v0.11.1)
### Added
- WebP Support ([069a0e4a](https://github.com/pixelfed/pixelfed/commit/069a0e4a))
- Auto Following support for admins ([68aa2540](https://github.com/pixelfed/pixelfed/commit/68aa2540))
- Mark as spammer mod tool, unlists and applies content warning to existing and future post ([6d956a86](https://github.com/pixelfed/pixelfed/commit/6d956a86))
- Diagnostics for error page and admin dashboard ([64725ecc](https://github.com/pixelfed/pixelfed/commit/64725ecc))
- Default media licenses and media license sync ([ea0fc90c](https://github.com/pixelfed/pixelfed/commit/ea0fc90c))
- Customize media description/alt-text length limit ([072d55d1](https://github.com/pixelfed/pixelfed/commit/072d55d1))
- Federate Media Licenses ([14a1367a](https://github.com/pixelfed/pixelfed/commit/14a1367a))
- Archive Posts ([e9ef0c88](https://github.com/pixelfed/pixelfed/commit/e9ef0c88))
- Polls ([77092200](https://github.com/pixelfed/pixelfed/commit/77092200))
- Federated Stories (#2895)
### Updated
- Updated PrettyNumber, fix deprecated warning. ([20ec870b](https://github.com/pixelfed/pixelfed/commit/20ec870b))
- Updated landing page, use config_cache. ([54920294](https://github.com/pixelfed/pixelfed/commit/54920294))
- Updated Timeline, implement suggested post opt out. ([66750d34](https://github.com/pixelfed/pixelfed/commit/66750d34))
- Updated Notification component, add at (@) symbol for remote profiles and local urls for remote posts and profile. ([aafd6a21](https://github.com/pixelfed/pixelfed/commit/aafd6a21))
- Updated Activity component, add at (@) symbol for remote profiles and local urls for remote posts and profile. ([a2211815](https://github.com/pixelfed/pixelfed/commit/a2211815))
- Updated Profile, add linkified bio, joined date, follows you label and improved website handling. ([8ee10436](https://github.com/pixelfed/pixelfed/commit/8ee10436))
- Updated routes, add legacy webfinger profile redirect. ([93c7af74](https://github.com/pixelfed/pixelfed/commit/93c7af74))
- Updated StoryController, fix expiration time bug. ([39e57f95](https://github.com/pixelfed/pixelfed/commit/39e57f95))
- Updated Profile component, fix remote urls. ([6e56dbed](https://github.com/pixelfed/pixelfed/commit/6e56dbed))
- Updated verify email screen, add contact admin link. ([f37952d6](https://github.com/pixelfed/pixelfed/commit/f37952d6))
- Updated RemoteProfile component, implement pagination. ([02b04a4b](https://github.com/pixelfed/pixelfed/commit/02b04a4b))
- Updated AP Helpers, generate notification for remote replies. ([8edd8294](https://github.com/pixelfed/pixelfed/commit/8edd8294))
- Updated like api, store status_profile_id and is_comment. ([c8c6b983](https://github.com/pixelfed/pixelfed/commit/c8c6b983))
- Updated Remote Post + Profile hashtag to redirect to local urls. ([1fa08644](https://github.com/pixelfed/pixelfed/commit/1fa08644))
- Updated Inbox, delete notifications on tombstone. ([ef63124d](https://github.com/pixelfed/pixelfed/commit/ef63124d))
- Updated NotificationCard, fix missing status bug. ([a3a86d46](https://github.com/pixelfed/pixelfed/commit/a3a86d46))
- Updated Activity component, fix comment bug. ([9a2db8eb](https://github.com/pixelfed/pixelfed/commit/9a2db8eb))
- Updated Inbox, fix tombstone bug. ([929ff5eb](https://github.com/pixelfed/pixelfed/commit/929ff5eb))
- Updated LikeService, skip self likes. ([3741c76d](https://github.com/pixelfed/pixelfed/commit/3741c76d))
- Updated StatusController, improve share api perf (11s to 72ms). ([d48ebb82](https://github.com/pixelfed/pixelfed/commit/d48ebb82))
- Updated ApiController, fix nulls in hashtag endpoint. ([f1208de0](https://github.com/pixelfed/pixelfed/commit/f1208de0))
- Updated SharePipeline, add Undo->Announce support. ([c8e40e0f](https://github.com/pixelfed/pixelfed/commit/c8e40e0f))
- Updated NetworkTimeline, fix remote comment urls. ([308acc91](https://github.com/pixelfed/pixelfed/commit/308acc91))
- Updated Timeline component, abstracted reusable partials. ([858f3f9e](https://github.com/pixelfed/pixelfed/commit/858f3f9e))
- Updated Timeline, fix suggested posts. ([3ba5c88c](https://github.com/pixelfed/pixelfed/commit/3ba5c88c))
- Updated Timeline, disable new post update checker and hide reaction bar on network timeline. ([1e3d3a69](https://github.com/pixelfed/pixelfed/commit/1e3d3a69))
- Updated PublicApiController, improve network timeline perf. ([e5f683fd](https://github.com/pixelfed/pixelfed/commit/e5f683fd))
- Updated Network Timeline, use existing Timeline component. ([0deaafc0](https://github.com/pixelfed/pixelfed/commit/0deaafc0))
- Updated PostComponent, show like count to owner using MomentUI. ([e9c46bab](https://github.com/pixelfed/pixelfed/commit/e9c46bab))
- Updated ContextMenu, add missing statusUrl method. ([3cffdb11](https://github.com/pixelfed/pixelfed/commit/3cffdb11))
- Updated PublicApiController, add LikeService to Network timeline. ([82895591](https://github.com/pixelfed/pixelfed/commit/82895591))
- Updated moderator api, expire cached status in StatusService. ([f215ee26](https://github.com/pixelfed/pixelfed/commit/f215ee26))
- Updated StatusHashtagService, fix null status bug. ([51a277e1](https://github.com/pixelfed/pixelfed/commit/51a277e1))
- Updated NotificationService, use zrevrangebyscore for api. ([d43e6d8d](https://github.com/pixelfed/pixelfed/commit/d43e6d8d))
- Updated ApiV1Controller, use PublicTimelineService. ([f67c67bc](https://github.com/pixelfed/pixelfed/commit/f67c67bc))
- Updated ApiV1Controller, use ProfileService for verify_credentials. ([352aa573](https://github.com/pixelfed/pixelfed/commit/352aa573))
- Updated RemotePost.vue, fix content warning button. ([7647e724](https://github.com/pixelfed/pixelfed/commit/7647e724))
- Updated AdminMediaController, improve perf and use simple pagination. ([f2686cac](https://github.com/pixelfed/pixelfed/commit/f2686cac))
- Updated PostComponent, fix MomentUI like counter. ([42c6121a](https://github.com/pixelfed/pixelfed/commit/42c6121a))
- Updated status views, remove like counts from status embed. ([1a2e41b1](https://github.com/pixelfed/pixelfed/commit/1a2e41b1))
- Updated Profile, fix unauthenticated private profiles. ([9017f7c4](https://github.com/pixelfed/pixelfed/commit/9017f7c4))
- Updated PublicApiController, impr home timeline perf. ([4fe42e5b](https://github.com/pixelfed/pixelfed/commit/4fe42e5b))
- Updated Timeline.vue, fix comment button. ([b6b5ce7c](https://github.com/pixelfed/pixelfed/commit/b6b5ce7c))
- Updated StatusEntityLexer, only add specific status types to PublicTimelineService. ([1fdcbe5b](https://github.com/pixelfed/pixelfed/commit/1fdcbe5b))
- Updated ActivityPub helpers, fix comment threading in statusFetch() method ([26b9c140](https://github.com/pixelfed/pixelfed/commit/26b9c140))
- Updated NotificationCard, fix typo in mention, share and comments. Fixes #2848. ([b37bb426](https://github.com/pixelfed/pixelfed/commit/b37bb426))
- Updated StatusCard.vue, add togglecw events to other presenters. ([9607243f](https://github.com/pixelfed/pixelfed/commit/9607243f))
- Updated presenters, fix content warning layout. ([fc56acb8](https://github.com/pixelfed/pixelfed/commit/fc56acb8))
- Updated reply blade view, fix missing avatar and media images. ([5fb33772](https://github.com/pixelfed/pixelfed/commit/5fb33772))
- Updated components, add fallback default avatar. ([726553f5](https://github.com/pixelfed/pixelfed/commit/726553f5))
- Updated job queue, separate deletes into their own queue. ([7f421392](https://github.com/pixelfed/pixelfed/commit/7f421392))
- Updated DiscoverController, use UserFilterService on trendingApi. ([135474ae](https://github.com/pixelfed/pixelfed/commit/135474ae))
- Updated PublicApiController, use UserFilterService in public timeline endpoint. ([ca6e491c](https://github.com/pixelfed/pixelfed/commit/ca6e491c))
- Updated ContextMenu, add View Profile link. ([8544bcbd](https://github.com/pixelfed/pixelfed/commit/8544bcbd))
- Updated presenters, improve content warnings. ([86422c81](https://github.com/pixelfed/pixelfed/commit/86422c81))
- Updated Timeline.vue, increase pagination limit from 3 to 12 and add empty feed placeholder. ([916e8f71](https://github.com/pixelfed/pixelfed/commit/916e8f71))
- Updated Timeline.vue, improve followed hashtags. ([728f10d7](https://github.com/pixelfed/pixelfed/commit/728f10d7))
- Updated PostComponent, use profileUrl method for comments. ([7ed65fc9](https://github.com/pixelfed/pixelfed/commit/7ed65fc9))
- Updated Timeline, fix empty timeline card. ([11eb6acd](https://github.com/pixelfed/pixelfed/commit/11eb6acd))
- Updated ap helpers, set text type when appropriate. ([9f4f983f](https://github.com/pixelfed/pixelfed/commit/9f4f983f))
- Updated StatusCard, add text support. ([ed14ee48](https://github.com/pixelfed/pixelfed/commit/ed14ee48))
- Updated PublicApiController, filter out text replies on home timeline. ([86219b57](https://github.com/pixelfed/pixelfed/commit/86219b57))
- Updated RemotePost.vue, improve text only post UI. ([b0257be2](https://github.com/pixelfed/pixelfed/commit/b0257be2))
- Updated Timeline, make text-only posts opt-in by default. ([0153ed6d](https://github.com/pixelfed/pixelfed/commit/0153ed6d))
- Updated LikeController, add UndoLikePipeline and federate Undo Like activities. ([8ac8fcad](https://github.com/pixelfed/pixelfed/commit/8ac8fcad))
- Updated Settings, add default license and enforced media descriptions. ([67e3f604](https://github.com/pixelfed/pixelfed/commit/67e3f604))
- Updated Compose Apis, make media descriptions/alt text length limit configurable. Default length: 1000. ([072d55d1](https://github.com/pixelfed/pixelfed/commit/072d55d1))
- Updated ApiV1Controller, add default license support. ([2a791f19](https://github.com/pixelfed/pixelfed/commit/2a791f19))
- Updated StatusTransformers, remove includes and use cached services. ([09d5198c](https://github.com/pixelfed/pixelfed/commit/09d5198c))
- Updated RemotePost component, update likes reaction bar. ([1060dd23](https://github.com/pixelfed/pixelfed/commit/1060dd23))
- Updated FollowPipeline, fix cache invalidation bug. ([c1f14f89](https://github.com/pixelfed/pixelfed/commit/c1f14f89))
- Updated PublicApiController, improve accountStatuses api perf. ([bce8edd9](https://github.com/pixelfed/pixelfed/commit/bce8edd9))
- Updated ApiControllers, use NotificationService. ([f9516ac3](https://github.com/pixelfed/pixelfed/commit/f9516ac3))
- Updated Notification components, fix old notifications with missing attributes. ([b6e226ae](https://github.com/pixelfed/pixelfed/commit/b6e226ae))
- Updated LikeController, improve query perf. ([f3d6023e](https://github.com/pixelfed/pixelfed/commit/f3d6023e))
- Updated License util, add nameToId method. ([f6131ed7](https://github.com/pixelfed/pixelfed/commit/f6131ed7))
- Updated RemoteProfile, add warning about potentially out of date information. ([7274574c](https://github.com/pixelfed/pixelfed/commit/7274574c))
- Updated NotifcationCard.vue component, add refresh button for cold notification cache. ([0e178a33](https://github.com/pixelfed/pixelfed/commit/0e178a33))
- Updated RemoteProfile component, add follower modals. ([c4146a30](https://github.com/pixelfed/pixelfed/commit/c4146a30))
- Updated FollowerService, cache audience. ([22257cc2](https://github.com/pixelfed/pixelfed/commit/22257cc2))
- Updated StatusService, add non-public option and improve cache invalidation. ([15c4fdd9](https://github.com/pixelfed/pixelfed/commit/15c4fdd9))
- Updated ContactAdmin mail, set New Support Message subject. ([bc3add05](https://github.com/pixelfed/pixelfed/commit/bc3add05))
- Updated StatusTransformer, prioritize scope over deprecated visibility attribute. ([6e45021f](https://github.com/pixelfed/pixelfed/commit/6e45021f))
- Updated StatusService, invalidate profile embed cache on deletion. ([acaf630d](https://github.com/pixelfed/pixelfed/commit/acaf630d))
- Updated status.reply view, fix archived post leakage. ([4fb3d1fa](https://github.com/pixelfed/pixelfed/commit/4fb3d1fa))
- Updated PostComponents, re-add time to timestamp. ([c5281dcd](https://github.com/pixelfed/pixelfed/commit/c5281dcd))
- Updated follow intent, fix follower count leak. ([03199e2f](https://github.com/pixelfed/pixelfed/commit/03199e2f))
- Updated Status model, add poll relation and allow up to 2 urls to autolink. ([2593cdee](https://github.com/pixelfed/pixelfed/commit/2593cdee))
- Updated snowflake id generation to improve randomness. ([e5aea490](https://github.com/pixelfed/pixelfed/commit/e5aea490))
- Updated Timeline, remove recent posts. ([7641b731](https://github.com/pixelfed/pixelfed/commit/7641b731))
- Updated InstanceCrawlPipeline, remove unused variable. ([e73cf531](https://github.com/pixelfed/pixelfed/commit/e73cf531))
- Updated StoryComposeController, fix expiry bug. ([7dee8f58](https://github.com/pixelfed/pixelfed/commit/7dee8f58))
- Updated Profile, fix following count bug. ([ee9f0795](https://github.com/pixelfed/pixelfed/commit/ee9f0795))
- Updated DirectMessageController, fix autocomplete bug. ([0f00be4d](https://github.com/pixelfed/pixelfed/commit/0f00be4d))
- Updated StoryService, fix division by zero bug. ([6ae1ba0a](https://github.com/pixelfed/pixelfed/commit/6ae1ba0a))
- Updated ApiV1Controller, fix empty public timeline bug. ([0584f9ee](https://github.com/pixelfed/pixelfed/commit/0584f9ee))
## [v0.11.0 (2021-06-01)](https://github.com/pixelfed/pixelfed/compare/v0.10.10...v0.11.0)
### Added
- Autocomplete Support (hashtags + mentions) ([de514f7d](https://github.com/pixelfed/pixelfed/commit/de514f7d))
- Creative Commons Licenses ([552e950](https://github.com/pixelfed/pixelfed/commit/552e950))
- Network Timeline ([af7face4](https://github.com/pixelfed/pixelfed/commit/af7face4))
- Admin config settings ([f2066b74](https://github.com/pixelfed/pixelfed/commit/f2066b74))
- Profile pronouns ([fabb57a9](https://github.com/pixelfed/pixelfed/commit/fabb57a9))
- Hashtag timeline api support ([241ae036](https://github.com/pixelfed/pixelfed/commit/241ae036))
- New admin dashboard layout ([eb7d5a4e](https://github.com/pixelfed/pixelfed/commit/eb7d5a4e))
- Fresh about page layout ([92dc7af6](https://github.com/pixelfed/pixelfed/commit/92dc7af6))
- Instance Rules ([a4efbb75](https://github.com/pixelfed/pixelfed/commit/a4efbb75))
- New Home Timeline ([56215be7](https://github.com/pixelfed/pixelfed/commit/56215be7))
### Updated
- Updated AdminController, fix variable name in updateSpam method. ([6edaf940](https://github.com/pixelfed/pixelfed/commit/6edaf940))
- Updated RemoteAvatarFetch, only dispatch jobs if cloud storage is enabled. ([4f40f6f5](https://github.com/pixelfed/pixelfed/commit/4f40f6f5))
- Updated StatusService, add ttl of 7 days. ([6e44ae0b](https://github.com/pixelfed/pixelfed/commit/6e44ae0b))
- Updated StatusHashtagService, use StatusService for statuses. ([0355b567](https://github.com/pixelfed/pixelfed/commit/0355b567))
- Updated StatusHashtagService, remove deprecated methods. ([aa4c718d](https://github.com/pixelfed/pixelfed/commit/aa4c718d))
- Updated ApiV1Controller, add StatusService del calls to update likes_count, reblogs_count and reply_count. ([05b9445c](https://github.com/pixelfed/pixelfed/commit/05b9445c))
- Updated Like, Status and Comment controllers to add StatusService del() method to update counts. ([eab4370c](https://github.com/pixelfed/pixelfed/commit/eab4370c))
- Updated ComposeController, use placeholder image for video media. Fixes #2595. ([789ed4b4](https://github.com/pixelfed/pixelfed/commit/789ed4b4))
- Updated DiscoverController, change api schema. ([2eea0409](https://github.com/pixelfed/pixelfed/commit/2eea0409))
- Updated StatusDelete pipeline, call StatusService::del() to remove status from cache. ([3f772ff8](https://github.com/pixelfed/pixelfed/commit/3f772ff8))
- Updated StatusHashtagTransformer, add blurhash attribute. ([899bbeba](https://github.com/pixelfed/pixelfed/commit/899bbeba))
- Updated status square previews, add blurhash and improved content warnings. ([39e389dd](https://github.com/pixelfed/pixelfed/commit/39e389dd))
- Updated Blurhash util, add default hash for invalid media. ([38a37c15](https://github.com/pixelfed/pixelfed/commit/38a37c15))
- Updated VideoThumbnail job, generate blurhash for videos. ([896452c7](https://github.com/pixelfed/pixelfed/commit/896452c7))
- Updated MediaTransformers, add default blurhash attribute. ([3f14a4c4](https://github.com/pixelfed/pixelfed/commit/3f14a4c4))
- Updated Timeline.vue, fix hashtag status previews. ([7768e844](https://github.com/pixelfed/pixelfed/commit/7768e844))
- Updated AP helpers, fix statusFetch 404s. ([3419379a](https://github.com/pixelfed/pixelfed/commit/3419379a))
- Updated InternalApiController, update discoverPosts method to improve performance. ([9862a855](https://github.com/pixelfed/pixelfed/commit/9862a855))
- Updated DiscoverComponent, add blurhash and like/comment counts. ([a8ebdd2e](https://github.com/pixelfed/pixelfed/commit/a8ebdd2e))
- Updated DiscoverComponent, add spinner loaders and remove deprecated sections. ([34869247](https://github.com/pixelfed/pixelfed/commit/34869247))
- Updated AccountController, add mutes and blocks endpoint to pixelfed api. ([1fb7e2b2](https://github.com/pixelfed/pixelfed/commit/1fb7e2b2))
- Updated AccountService, cache object and observe changes. ([b299da93](https://github.com/pixelfed/pixelfed/commit/b299da93))
- Updated webfinger util, fail on invalid webfinger url. Fixes ([#2613](https://github.com/pixelfed/pixelfed/issues/2613)) ([2d11317c](https://github.com/pixelfed/pixelfed/commit/2d11317c))
- Updated MediaStorageService, dispatch deletes to MediaDeletePipeline. ([37dbb3de](https://github.com/pixelfed/pixelfed/commit/37dbb3de))
- Updated ComposeController, use MediaStorageService for media deletes. ([ab5469ff](https://github.com/pixelfed/pixelfed/commit/ab5469ff))
- Updated StatusDeletePipeline, use MediaStorageService for media deletes. ([9fd90e17](https://github.com/pixelfed/pixelfed/commit/9fd90e17))
- Updated Discover, allow public discover access. ([1404ac6e](https://github.com/pixelfed/pixelfed/commit/1404ac6e))
- Updated pixelfed config, add media_fast_process setting. ([6bee5072](https://github.com/pixelfed/pixelfed/commit/6bee5072))
- Updated ComposeController, add mediaProcessingCheck method. ([33b625f5](https://github.com/pixelfed/pixelfed/commit/33b625f5))
- Updated ComposeModal, add processing step disabled by default. ([e6e76e80](https://github.com/pixelfed/pixelfed/commit/e6e76e80))
- Updated DiscoverComponent, allow unauthenticated if enabled. ([a1059a6e](https://github.com/pixelfed/pixelfed/commit/a1059a6e))
- Updated components, improve content warnings. ([a9e98965](https://github.com/pixelfed/pixelfed/commit/a9e98965))
- Updated ComposeModal, prevent tagging empty users. Fixes #2633. ([ceae664c](https://github.com/pixelfed/pixelfed/commit/ceae664c))
- Updated ComposeModal, show filter warning for unsupported browsers. ([12ce7602](https://github.com/pixelfed/pixelfed/commit/12ce7602))
- Updated Hashtag component, fix null infinite loading bug. Fixes #2637. ([55136518](https://github.com/pixelfed/pixelfed/commit/55136518))
- Updated filesystems config, add backup driver to store backups on other filesystems. ([ae90eef9](https://github.com/pixelfed/pixelfed/commit/ae90eef9))
- Updated Embeds. Fix Profile + Status embeds, remove following count and improve cache invalidation and hidden follower counts. ([5ac9d0e8](https://github.com/pixelfed/pixelfed/commit/5ac9d0e8))
- Updated FederationController, return 404 for invalid webfinger addresses. Fixes ([#2647](https://github.com/pixelfed/pixelfed/issues/2647)). ([deb6f115](https://github.com/pixelfed/pixelfed/commit/deb6f115))
- Updated InboxPipeline, fail earlier for invalid public keys. Fixes ([#2648](https://github.com/pixelfed/pixelfed/issues/2648)). ([d1c5e9b8](https://github.com/pixelfed/pixelfed/commit/d1c5e9b8))
- Updated Status model, refactor liked and shared methods to fix cache invalidation bug. ([f05c3b66](https://github.com/pixelfed/pixelfed/commit/f05c3b66))
- Updated Timeline component, add inline reports modal. ([e64b4bd3](https://github.com/pixelfed/pixelfed/commit/e64b4bd3))
- Updated federation pipeline, add locks. ([ddc76887](https://github.com/pixelfed/pixelfed/commit/ddc76887))
- Updated MediaStorageService, improve head checks to fix failed jobs. ([1769cdfd](https://github.com/pixelfed/pixelfed/commit/1769cdfd))
- Updated user admin, remove expensive db query and add search. ([8feeadbf](https://github.com/pixelfed/pixelfed/commit/8feeadbf))
- Updated Compose apis, prevent private accounts from posting public or unlisted scopes. ([f53bfa6f](https://github.com/pixelfed/pixelfed/commit/f53bfa6f))
- Updated font icons, use font-display:swap. ([77d4353a](https://github.com/pixelfed/pixelfed/commit/77d4353a))
- Updated ComposeModal, limit visibility scope for private accounts. ([001d4105](https://github.com/pixelfed/pixelfed/commit/001d4105))
- Updated ComposeController, add autocomplete apis for hashtags and mentions. ([f0e48a09](https://github.com/pixelfed/pixelfed/commit/f0e48a09))
- Updated StatusController, invalidate profile embed cache on status delete. ([9c8a87c3](https://github.com/pixelfed/pixelfed/commit/9c8a87c3))
- Updated moderation api, invalidate profile embed. ([b2501bfc](https://github.com/pixelfed/pixelfed/commit/b2501bfc))
- Updated Nodeinfo util, use last_active_at for monthly active user count. ([d200c12c](https://github.com/pixelfed/pixelfed/commit/d200c12c))
- Updated PhotoPresenter, add width and height to images. ([3f8202e2](https://github.com/pixelfed/pixelfed/commit/3f8202e2))
- Updated Compose Apis, refactor rate limits. ([42375b3d](https://github.com/pixelfed/pixelfed/commit/42375b3d))
- Updated PublicApiController, show unlisted comments. ([e1c6297e](https://github.com/pixelfed/pixelfed/commit/e1c6297e))
- Updated ApiV1Controller, add missing variable. ([886ea617](https://github.com/pixelfed/pixelfed/commit/886ea617))
- Updated PublicApiController, limit network pagination to 3 months. ([10119bbb](https://github.com/pixelfed/pixelfed/commit/10119bbb))
- Updated admin instance page, add search and improve performance. ([f5829373](https://github.com/pixelfed/pixelfed/commit/f5829373))
- Updated AdminInstanceController, invalidate banned domain cache when updated. ([35393edf](https://github.com/pixelfed/pixelfed/commit/35393edf))
- Updated AP Helpers, use instance filtering. ([66b4f8c7](https://github.com/pixelfed/pixelfed/commit/66b4f8c7))
- Updated ApiV1Controller, add missing instance api attributes. ([64b86546](https://github.com/pixelfed/pixelfed/commit/64b86546))
- Updated story garbage collection, handle non active stories and new ephemeral story media directory. ([c43f8bcc](https://github.com/pixelfed/pixelfed/commit/c43f8bcc))
- Updated Stories, add crop and duration settings to composer. ([c8edca69](https://github.com/pixelfed/pixelfed/commit/c8edca69))
- Updated instance endpoint, add custom description. ([668e936e](https://github.com/pixelfed/pixelfed/commit/668e936e))
- Updated StoryCompose component, improve full screen preview. ([39a76103](https://github.com/pixelfed/pixelfed/commit/39a76103))
- Updated Helpers, fix broken tests. ([22dddaa0](https://github.com/pixelfed/pixelfed/commit/22dddaa0))
- Updated StoryController, fix cache crop bug. ([c2f8faae](https://github.com/pixelfed/pixelfed/commit/c2f8faae))
- Updated StoryController, optimize photo size by resizing to 9:16 aspect. ([e66ed9a2](https://github.com/pixelfed/pixelfed/commit/e66ed9a2))
- Updated StoryCompose crop logic. ([2ead622c](https://github.com/pixelfed/pixelfed/commit/2ead622c))
- Updated StatusController, allow license edits without 24 hour limit. ([c799a01a](https://github.com/pixelfed/pixelfed/commit/c799a01a))
- Updated Settings, remove reports page. ([9cf962ff](https://github.com/pixelfed/pixelfed/commit/9cf962ff))
- Updated ProfileService, use account transformer. ([391b1287](https://github.com/pixelfed/pixelfed/commit/391b1287))
- Updated LikeController, hide like counts. ([ea687240](https://github.com/pixelfed/pixelfed/commit/ea687240))
- Updated StatusTransformers, add liked_by attribute. ([372bacb0](https://github.com/pixelfed/pixelfed/commit/372bacb0))
- Updated PostComponent, change like logic. ([0a35f5d6](https://github.com/pixelfed/pixelfed/commit/0a35f5d6))
- Updated Timeline component, change like logic. ([7bcbf96b](https://github.com/pixelfed/pixelfed/commit/7bcbf96b))
- Updated LikeService, fix likedBy method. ([a5e64da6](https://github.com/pixelfed/pixelfed/commit/a5e64da6))
- Updated PublicApiController, increase public timeline to 6 months from 3. ([8a736432](https://github.com/pixelfed/pixelfed/commit/8a736432))
- Updated LikeService, show like count to status owner. ([4408e2ef](https://github.com/pixelfed/pixelfed/commit/4408e2ef))
- Updated admin settings, add rules. ([a4efbb75](https://github.com/pixelfed/pixelfed/commit/a4efbb75))
- Updated LikeService, fix authentication bug. ([c9abd70e](https://github.com/pixelfed/pixelfed/commit/c9abd70e))
- Updated StatusTransformer, fix missing tags attribute. ([dac326e9](https://github.com/pixelfed/pixelfed/commit/dac326e9))
- Updated ComposeController, bail on empty attachments. ([061b145b](https://github.com/pixelfed/pixelfed/commit/061b145b))
- Updated landing and about page. ([92dc7af6](https://github.com/pixelfed/pixelfed/commit/92dc7af6))
- Updated AdminStatsService, fix postgres bug. ([af719135](https://github.com/pixelfed/pixelfed/commit/af719135))
- Updated api, remove auth requirement for hashtag timeline. ([c8e43c60](https://github.com/pixelfed/pixelfed/commit/c8e43c60))
- Updated NotificationCard component, fix default value. ([78ad4e77](https://github.com/pixelfed/pixelfed/commit/78ad4e77))
- Updated Timeline component, show counts and make sidebar footer lighter. ([0788bffa](https://github.com/pixelfed/pixelfed/commit/0788bffa))
- Updated AuthServiceProvider, increase default token + refresh token lifetime. ([178ed63d](https://github.com/pixelfed/pixelfed/commit/178ed63d))
- Updated liked by, fix remote username urls. ([f767d99a](https://github.com/pixelfed/pixelfed/commit/f767d99a))
- Updated StatusController, add cache invalidation for timeline cursor. ([f3bf2fd4](https://github.com/pixelfed/pixelfed/commit/f3bf2fd4))
- Updated PublicApiController, add recent feed support to home timeline. ([1e230e80](https://github.com/pixelfed/pixelfed/commit/1e230e80))
- Updated Inbox, fix reply/comment bug by moving attachment validation to Note with attachments. ([28df9f7e](https://github.com/pixelfed/pixelfed/commit/28df9f7e))
- Updated PrettyNumber, add decimal option. ([84520fe1](https://github.com/pixelfed/pixelfed/commit/84520fe1))
- Updated app config, change default descriptions. ([7d24560d](https://github.com/pixelfed/pixelfed/commit/7d24560d))
- Updated NotificationCard, fix loading bug. ([69567e19](https://github.com/pixelfed/pixelfed/commit/69567e19))
- Updated DirectMessageController, disable exception logging for invalid urls. Fixes ([#2752](https://github.com/pixelfed/pixelfed/issues/2752)). ([2d0a253e](https://github.com/pixelfed/pixelfed/commit/2d0a253e))
## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10)
### Added ### Added
- Direct Messages ([d63569c](https://github.com/pixelfed/pixelfed/commit/d63569c)) - Direct Messages ([d63569c](https://github.com/pixelfed/pixelfed/commit/d63569c))
- ActivityPubFetchService for signed GET requests ([8763bfc5](https://github.com/pixelfed/pixelfed/commit/8763bfc5)) - ActivityPubFetchService for signed GET requests ([8763bfc5](https://github.com/pixelfed/pixelfed/commit/8763bfc5)) ([3ee1215a](https://github.com/pixelfed/pixelfed/commit/3ee1215a))
- Custom content warnings for remote posts ([6afc61a4](https://github.com/pixelfed/pixelfed/commit/6afc61a4)) - Custom content warnings for remote posts ([6afc61a4](https://github.com/pixelfed/pixelfed/commit/6afc61a4))
- Thai translations ([74cd536](https://github.com/pixelfed/pixelfed/commit/74cd536)) - Thai translations ([74cd536](https://github.com/pixelfed/pixelfed/commit/74cd536))
- Added Bookmarks to v1 api ([99cb48c5](https://github.com/pixelfed/pixelfed/commit/99cb48c5)) - Added Bookmarks to v1 api ([99cb48c5](https://github.com/pixelfed/pixelfed/commit/99cb48c5))
- Added New Post notification to Timeline ([a0e7c4d5](https://github.com/pixelfed/pixelfed/commit/a0e7c4d5)) - Added New Post notification to Timeline ([a0e7c4d5](https://github.com/pixelfed/pixelfed/commit/a0e7c4d5))
- Add Instagram Import ([e2a6bdd0](https://github.com/pixelfed/pixelfed/commit/e2a6bdd0)) - Add Instagram Import ([e2a6bdd0](https://github.com/pixelfed/pixelfed/commit/e2a6bdd0))
- Add notification preview to NotificationCard ([28445e27](https://github.com/pixelfed/pixelfed/commit/28445e27)) - Add notification preview to NotificationCard ([28445e27](https://github.com/pixelfed/pixelfed/commit/28445e27))
- Add Grid Mode to Timelines ([c1853ca8](https://github.com/pixelfed/pixelfed/commit/c1853ca8))
- Add MediaPathService ([c54b29c5](https://github.com/pixelfed/pixelfed/commit/c54b29c5)) - Add MediaPathService ([c54b29c5](https://github.com/pixelfed/pixelfed/commit/c54b29c5))
- Add Media Tags ([711fc020](https://github.com/pixelfed/pixelfed/commit/711fc020)) - Add Media Tags ([711fc020](https://github.com/pixelfed/pixelfed/commit/711fc020))
- Add MediaTagService ([524c6d45](https://github.com/pixelfed/pixelfed/commit/524c6d45)) - Add MediaTagService ([524c6d45](https://github.com/pixelfed/pixelfed/commit/524c6d45))
@ -24,6 +373,7 @@
- Add autospam feature ([b892bcf0](https://github.com/pixelfed/pixelfed/commit/b892bcf0)) - Add autospam feature ([b892bcf0](https://github.com/pixelfed/pixelfed/commit/b892bcf0))
- Add hCaptcha ([082c1ccb](https://github.com/pixelfed/pixelfed/commit/082c1ccb)) - Add hCaptcha ([082c1ccb](https://github.com/pixelfed/pixelfed/commit/082c1ccb))
- Add StatusView model to store views for discover algorithm ([7a68ee94](https://github.com/pixelfed/pixelfed/commit/7a68ee94)) - Add StatusView model to store views for discover algorithm ([7a68ee94](https://github.com/pixelfed/pixelfed/commit/7a68ee94))
- Add Year in Review feature (mysql only) ([f32072a3](https://github.com/pixelfed/pixelfed/commit/f32072a3))
### Updated ### Updated
- Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc)) - Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc))
@ -145,8 +495,8 @@
- Updated avatars, use jpeg default. ([f6528c84](https://github.com/pixelfed/pixelfed/commit/f6528c84)) - Updated avatars, use jpeg default. ([f6528c84](https://github.com/pixelfed/pixelfed/commit/f6528c84))
- Updated antispam bouncer, change recent from 1 week to 3 months. ([7d818197](https://github.com/pixelfed/pixelfed/commit/7d818197)) - Updated antispam bouncer, change recent from 1 week to 3 months. ([7d818197](https://github.com/pixelfed/pixelfed/commit/7d818197))
- Updated Post components, fix remote post and profile urls. ([cfcf17f3](https://github.com/pixelfed/pixelfed/commit/cfcf17f3)) - Updated Post components, fix remote post and profile urls. ([cfcf17f3](https://github.com/pixelfed/pixelfed/commit/cfcf17f3))
- Update migrations, fix broken oauth change. ([4a885c88](https://github.com/pixelfed/pixelfed/commit/4a885c88)) - Updated migrations, fix broken oauth change. ([4a885c88](https://github.com/pixelfed/pixelfed/commit/4a885c88))
- Update LikeController, store status_profile_id and is_comment attributes. ([799a4cba](https://github.com/pixelfed/pixelfed/commit/799a4cba)) - Updated LikeController, store status_profile_id and is_comment attributes. ([799a4cba](https://github.com/pixelfed/pixelfed/commit/799a4cba))
- Updated Profile, fix status count. ([6dcd472b](https://github.com/pixelfed/pixelfed/commit/6dcd472b)) - Updated Profile, fix status count. ([6dcd472b](https://github.com/pixelfed/pixelfed/commit/6dcd472b))
- Updated StatusService, cast response to array. ([0fbde91e](https://github.com/pixelfed/pixelfed/commit/0fbde91e)) - Updated StatusService, cast response to array. ([0fbde91e](https://github.com/pixelfed/pixelfed/commit/0fbde91e))
- Updated status model, use scope over deprecated visibility attribute. ([f70826e1](https://github.com/pixelfed/pixelfed/commit/f70826e1)) - Updated status model, use scope over deprecated visibility attribute. ([f70826e1](https://github.com/pixelfed/pixelfed/commit/f70826e1))
@ -155,7 +505,44 @@
- Updated AP helpers, fixed federation bug. ([a52564f3](https://github.com/pixelfed/pixelfed/commit/a52564f3)) - Updated AP helpers, fixed federation bug. ([a52564f3](https://github.com/pixelfed/pixelfed/commit/a52564f3))
- Updated Helpers, cache profiles. ([1f672ecf](https://github.com/pixelfed/pixelfed/commit/1f672ecf)) - Updated Helpers, cache profiles. ([1f672ecf](https://github.com/pixelfed/pixelfed/commit/1f672ecf))
- Updated DiscoverController, improve trending api performance. ([d8d3331f](https://github.com/pixelfed/pixelfed/commit/d8d3331f)) - Updated DiscoverController, improve trending api performance. ([d8d3331f](https://github.com/pixelfed/pixelfed/commit/d8d3331f))
- Update InboxWorker, fix race condition in account deletes. ([4a4d8f00](https://github.com/pixelfed/pixelfed/commit/4a4d8f00)) - Updated InboxWorker, fix race condition in account deletes. ([4a4d8f00](https://github.com/pixelfed/pixelfed/commit/4a4d8f00))
- Updated StoryItemTransformer, increase story duration from 5 seconds to 10 seconds. ([5b0b14fc](https://github.com/pixelfed/pixelfed/commit/5b0b14fc))
- Updated StatusController, add view method. ([0cfc12c5](https://github.com/pixelfed/pixelfed/commit/0cfc12c5))
- Updated MediaPathService, add story method. ([aac44309](https://github.com/pixelfed/pixelfed/commit/aac44309))
- Updated StatusDelete job, handle cloud storage media deletes. ([4b1a0fd7](https://github.com/pixelfed/pixelfed/commit/4b1a0fd7))
- Updated ImageOptimizePipeline, add skip_optimize and MediaStorageService support. ([234f72f3](https://github.com/pixelfed/pixelfed/commit/234f72f3))
- Updated Media model, add cdn support to url and thumbnailUrl methods. ([57fa889d](https://github.com/pixelfed/pixelfed/commit/57fa889d))
- Updated MediaController, remove deprecated endpoint. ([8132db74](https://github.com/pixelfed/pixelfed/commit/8132db74))
- Updated api controllers, deprecate old endpoints. ([4415af1b](https://github.com/pixelfed/pixelfed/commit/4415af1b))
- Updated mobile apis, add blurhash. ([cf40526e](https://github.com/pixelfed/pixelfed/commit/cf40526e))
- Updated Image media util, store dimensions of media not thumbnail. ([40bd64aa](https://github.com/pixelfed/pixelfed/commit/40bd64aa))
- Updated MediaTransformers, include meta attribute with focus and dimensions. ([f8cbe1e4](https://github.com/pixelfed/pixelfed/commit/f8cbe1e4))
- Updated storage, add remote media cache directory. ([0eabbfdd](https://github.com/pixelfed/pixelfed/commit/0eabbfdd))
- Updated backup config, prevents gateway timeouts for large databases using mysql. ([9cd4bd74](https://github.com/pixelfed/pixelfed/commit/9cd4bd74))
- Updated MediaPipeline, handle cloud object storage. ([be6d12fc](https://github.com/pixelfed/pixelfed/commit/be6d12fc))
- Updated AP Helpers, use MediaStoragePipeline. ([01a1ffd6](https://github.com/pixelfed/pixelfed/commit/01a1ffd6))
- Updated RemoteProfile component, change thumbnail url. ([c1118956](https://github.com/pixelfed/pixelfed/commit/c1118956))
- Updated blade views. ([9683e846](https://github.com/pixelfed/pixelfed/commit/9683e846))
- Updated cache config, use phpredis by default. ([ed6877df](https://github.com/pixelfed/pixelfed/commit/ed6877df))
- Updated components, fix url rewriter. Closes #2538. ([e8cc66dc](https://github.com/pixelfed/pixelfed/commit/e8cc66dc))
- Updated UserCreate command, closes #2581. ([b2b8c9f9](https://github.com/pixelfed/pixelfed/commit/b2b8c9f9))
- Updated AvatarController, remove deprecated thumb_path. ([889c3d87](https://github.com/pixelfed/pixelfed/commit/889c3d87))
- Updated VideoThumbnail, add MediaStoragePipeline. ([98c44f7b](https://github.com/pixelfed/pixelfed/commit/98c44f7b))
- Updated StatusDelete pipeline, fix object storage thumbnail deletion. ([f930c4bd](https://github.com/pixelfed/pixelfed/commit/f930c4bd))
- Updated MediaStorageService, clear transformer cache after storing media. ([ce6ab80d](https://github.com/pixelfed/pixelfed/commit/ce6ab80d))
- Updated MediaTransformer, remove cache busting. ([258b2729](https://github.com/pixelfed/pixelfed/commit/258b2729))
- Updated AP helpers, only run MediaStoragePipeline if using cloud storage. ([77f21b4b](https://github.com/pixelfed/pixelfed/commit/77f21b4b))
- Updated AvatarObserver, add logic to delete avatars stored in S3. ([9eafc31e](https://github.com/pixelfed/pixelfed/commit/9eafc31e))
- Updated Profile model, use cdn_url for avatars. ([ea8e4261](https://github.com/pixelfed/pixelfed/commit/ea8e4261))
- Updated ActivityPubFetchService, add url validation. ([654b08d3](https://github.com/pixelfed/pixelfed/commit/654b08d3))
- Updated MediaStorageService, add avatar method. ([94a9f685](https://github.com/pixelfed/pixelfed/commit/94a9f685))
- Updated AvatarPipeline, add remote avatar fetch. ([4c148055](https://github.com/pixelfed/pixelfed/commit/4c148055))
- Updated ComposeController, update media version. ([cc2d4bf8](https://github.com/pixelfed/pixelfed/commit/cc2d4bf8))
- Updated AP Helpers, add blurhash and RemoteAvatarFetch. ([de8828e8](https://github.com/pixelfed/pixelfed/commit/de8828e8))
- Updated Timeline, prevent nextTick() when reloading same comment modal. Fixes #2584. ([cc84125b](https://github.com/pixelfed/pixelfed/commit/cc84125b))
- Updated site config, add labels to config. ([abe9cb3d](https://github.com/pixelfed/pixelfed/commit/abe9cb3d))
- Update StatusLabelService, change config key. ([4abfe76a](https://github.com/pixelfed/pixelfed/commit/4abfe76a))
## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9) ## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9)
### Added ### Added
@ -213,7 +600,7 @@
- Updated StatusTransformer, fixes #[2113](https://github.com/pixelfed/pixelfed/issues/2113) ([eefa6e0d](https://github.com/pixelfed/pixelfed/commit/eefa6e0d)) - Updated StatusTransformer, fixes #[2113](https://github.com/pixelfed/pixelfed/issues/2113) ([eefa6e0d](https://github.com/pixelfed/pixelfed/commit/eefa6e0d))
- Updated InternalApiController, limit remote profile ui to remote profiles ([d918a68e](https://github.com/pixelfed/pixelfed/commit/d918a68e)) - Updated InternalApiController, limit remote profile ui to remote profiles ([d918a68e](https://github.com/pixelfed/pixelfed/commit/d918a68e))
- Updated NotificationCard, fix pagination bug #[2019](https://github.com/pixelfed/pixelfed/issues/2019) ([32beaad5](https://github.com/pixelfed/pixelfed/commit/32beaad5)) - Updated NotificationCard, fix pagination bug #[2019](https://github.com/pixelfed/pixelfed/issues/2019) ([32beaad5](https://github.com/pixelfed/pixelfed/commit/32beaad5))
-
## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8) ## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8)
### Added ### Added

View file

@ -11,13 +11,19 @@
A free and ethical photo sharing platform, powered by ActivityPub federation. A free and ethical photo sharing platform, powered by ActivityPub federation.
<p align="center"> <p align="center">
<img src="https://pixelfed.nyc3.cdn.digitaloceanspaces.com/media/Screen%20Shot%202019-09-08%20at%2010.40.54%20PM.png"> <img src="https://pixelfed.nyc3.cdn.digitaloceanspaces.com/media/pixelfed-screenshot.jpg">
</p> </p>
## Official Documentation ## Official Documentation
Documentation for Pixelfed can be found on the [Pixelfed documentation website](https://docs.pixelfed.org/). Documentation for Pixelfed can be found on the [Pixelfed documentation website](https://docs.pixelfed.org/).
## Run on YunoHost
[![Install on YunoHost](https://user-images.githubusercontent.com/42862428/139559471-9495f1e9-e7a4-49f1-9a4b-675ddcc510a2.png 'Install on YunoHost')](https://install-app.yunohost.org/?app=pixelfed)
Pixelfed app for [YunoHost](https://yunohost.org 'YunoHost'). See [the package source code](https://github.com/YunoHost-Apps/pixelfed_ynh 'pixelfed_ynh repository on GitHub')
## License ## License
Pixelfed is open-sourced software licensed under the AGPL license. Pixelfed is open-sourced software licensed under the AGPL license.
@ -27,7 +33,6 @@ Pixelfed is open-sourced software licensed under the AGPL license.
The ways you can communicate on the project are below. Before interacting, please The ways you can communicate on the project are below. Before interacting, please
read through the [Code Of Conduct](CODE_OF_CONDUCT.md). read through the [Code Of Conduct](CODE_OF_CONDUCT.md).
* IRC: [#pixelfed](irc://chat.freenode.net/pixelfed) on irc.freenode.net
* Mastodon: [@pixelfed@mastodon.social](https://mastodon.social/@pixelfed) * Mastodon: [@pixelfed@mastodon.social](https://mastodon.social/@pixelfed)
* E-mail: [hello@pixelfed.org](mailto:hello@pixelfed.org) * E-mail: [hello@pixelfed.org](mailto:hello@pixelfed.org)

3
SECURITY.md Normal file
View file

@ -0,0 +1,3 @@
## Reporting a Vulnerability
If you discover any security related issues, please email hello@pixelfed.org instead of using the issue tracker.

View file

@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model;
class AccountLog extends Model class AccountLog extends Model
{ {
protected $fillable = ['*'];
public function user() public function user()
{ {
return $this->belongsTo(User::class); return $this->belongsTo(User::class);

View file

@ -14,9 +14,21 @@ class Avatar extends Model
* *
* @var array * @var array
*/ */
protected $dates = ['deleted_at']; protected $dates = [
'deleted_at',
'last_fetched_at',
'last_processed_at'
];
protected $fillable = ['profile_id']; protected $fillable = ['profile_id'];
protected $visible = [
'id',
'profile_id',
'media_path',
'size',
];
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class); return $this->belongsTo(Profile::class);

View file

@ -4,7 +4,7 @@ namespace App;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Pixelfed\Snowflake\HasSnowflakePrimary; use App\HasSnowflakePrimary;
class Collection extends Model class Collection extends Model
{ {

View file

@ -3,7 +3,7 @@
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Pixelfed\Snowflake\HasSnowflakePrimary; use App\HasSnowflakePrimary;
class CollectionItem extends Model class CollectionItem extends Model
{ {

View file

@ -0,0 +1,219 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Avatar;
use App\Profile;
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
use App\Util\ActivityPub\Helpers;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class AvatarSync extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'avatars:sync';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Perform actions on avatars';
public $found = 0;
public $notFetched = 0;
public $fixed = 0;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info('Welcome to the avatar sync manager');
$actions = [
'Analyze',
'Full Analyze',
'Fetch - Fetch missing remote avatars',
'Fix - Fix remote accounts without avatar record',
'Sync - Store latest remote avatars',
];
$name = $this->choice(
'Select an action',
$actions,
0,
1,
false
);
$this->info('Selected: ' . $name);
switch($name) {
case $actions[0]:
$this->analyze();
break;
case $actions[1]:
$this->fullAnalyze();
break;
case $actions[2]:
$this->fetch();
break;
case $actions[3]:
$this->fix();
break;
case $actions[4]:
$this->sync();
break;
}
return Command::SUCCESS;
}
protected function incr($name)
{
switch($name) {
case 'found':
$this->found = $this->found + 1;
break;
case 'notFetched':
$this->notFetched = $this->notFetched + 1;
break;
case 'fixed':
$this->fixed++;
break;
}
}
protected function analyze()
{
$count = Avatar::whereIsRemote(true)->whereNull('cdn_url')->count();
$this->info('Found ' . $count . ' profiles with blank avatars.');
$this->line(' ');
$this->comment('We suggest running php artisan avatars:sync again and selecting the sync option');
$this->line(' ');
}
protected function fullAnalyze()
{
$count = Profile::count();
$bar = $this->output->createProgressBar($count);
$bar->start();
Profile::chunk(5000, function($profiles) use ($bar) {
foreach($profiles as $profile) {
if($profile->domain == null) {
$bar->advance();
continue;
}
$avatar = Avatar::whereProfileId($profile->id)->first();
if(!$avatar || $avatar->cdn_url == null) {
$this->incr('notFetched');
}
$this->incr('found');
$bar->advance();
}
});
$this->line(' ');
$this->line(' ');
$this->info('Found ' . $this->found . ' remote accounts');
$this->info('Found ' . $this->notFetched . ' remote avatars to fetch');
}
protected function fetch()
{
$this->info('Fetching ....');
Avatar::whereIsRemote(true)
->whereNull('cdn_url')
// ->with('profile')
->chunk(10, function($avatars) {
foreach($avatars as $avatar) {
if(!$avatar || !$avatar->profile) {
continue;
}
$url = $avatar->profile->remote_url;
if(!$url || !Helpers::validateUrl($url)) {
continue;
}
try {
$res = Helpers::fetchFromUrl($url);
if(
!is_array($res) ||
!isset($res['@context']) ||
!isset($res['icon']) ||
!isset($res['icon']['type']) ||
!isset($res['icon']['url']) ||
!Str::endsWith($res['icon']['url'], ['.png', '.jpg', '.jpeg'])
) {
continue;
}
} catch (\GuzzleHttp\Exception\RequestException $e) {
continue;
} catch(\Illuminate\Http\Client\ConnectionException $e) {
continue;
}
$avatar->remote_url = $res['icon']['url'];
$avatar->save();
RemoteAvatarFetch::dispatch($avatar->profile);
}
});
}
protected function fix()
{
Profile::chunk(5000, function($profiles) {
foreach($profiles as $profile) {
if($profile->domain == null || $profile->private_key) {
continue;
}
$avatar = Avatar::whereProfileId($profile->id)->first();
if($avatar) {
continue;
}
$avatar = new Avatar;
$avatar->is_remote = true;
$avatar->profile_id = $profile->id;
$avatar->save();
$this->incr('fixed');
}
});
$this->line(' ');
$this->line(' ');
$this->info('Fixed ' . $this->fixed . ' accounts with a blank avatar');
}
protected function sync()
{
Avatar::whereIsRemote(true)
->with('profile')
->chunk(10, function($avatars) {
foreach($avatars as $avatar) {
RemoteAvatarFetch::dispatch($avatar->profile);
}
});
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace App\Console\Commands;
use Illuminate\Http\File;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Spatie\Backup\BackupDestination\BackupDestination;
class BackupToCloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'backup:cloud';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send backups to cloud storage';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$localDisk = Storage::disk('local');
$cloudDisk = Storage::disk('backup');
$backupDestination = new BackupDestination($localDisk, '', 'local');
if(
empty(config('filesystems.disks.backup.key')) ||
empty(config('filesystems.disks.backup.secret')) ||
empty(config('filesystems.disks.backup.endpoint')) ||
empty(config('filesystems.disks.backup.region')) ||
empty(config('filesystems.disks.backup.bucket'))
) {
$this->error('Backup disk not configured.');
$this->error('See https://docs.pixelfed.org/technical-documentation/env.html#filesystem for more information.');
return Command::FAILURE;
}
$newest = $backupDestination->newestBackup();
$name = $newest->path();
$parts = explode('/', $name);
$fileName = array_pop($parts);
$storagePath = 'backups';
$path = storage_path('app/'. $name);
$file = $cloudDisk->putFileAs($storagePath, new File($path), $fileName, 'private');
$this->info("Backup file successfully saved!");
$url = $cloudDisk->url($file);
$this->table(
['Name', 'URL'],
[
[$fileName, $url]
],
);
return Command::SUCCESS;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class DatabaseSessionGarbageCollector extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'gc:sessions';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Database sessions garbage collector';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if(config('session.driver') !== 'database') {
return Command::SUCCESS;
}
DB::transaction(function() {
DB::table('sessions')->whereNull('user_id')->delete();
});
DB::transaction(function() {
$ts = now()->subMonths(3)->timestamp;
DB::table('sessions')->where('last_activity', '<', $ts)->delete();
});
return Command::SUCCESS;
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class ExportLanguages extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'i18n:export';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Build and export js localization files.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if(config('app.env') !== 'local') {
$this->error('This command is meant for development purposes and should only be run in a local environment');
return Command::FAILURE;
}
$path = base_path('resources/lang');
$langs = [];
foreach (new \DirectoryIterator($path) as $io) {
$name = $io->getFilename();
$skip = ['vendor'];
if($io->isDot() || in_array($name, $skip)) {
continue;
}
if($io->isDir()) {
array_push($langs, $name);
}
}
$exportDir = resource_path('assets/js/i18n/');
$exportDirAlt = public_path('_lang/');
foreach($langs as $lang) {
$strings = \Lang::get('web', [], $lang);
$json = json_encode($strings, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
$path = "{$exportDir}{$lang}.json";
file_put_contents($path, $json);
$pathAlt = "{$exportDirAlt}{$lang}.json";
file_put_contents($pathAlt, $json);
}
return Command::SUCCESS;
}
}

View file

@ -40,7 +40,7 @@ class FailedJobGC extends Command
{ {
FailedJob::chunk(50, function($jobs) { FailedJob::chunk(50, function($jobs) {
foreach($jobs as $job) { foreach($jobs as $job) {
if($job->failed_at->lt(now()->subMonth())) { if($job->failed_at->lt(now()->subHours(48))) {
$job->delete(); $job->delete();
} }
} }

View file

@ -65,7 +65,6 @@ class ImportCities extends Command
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
ini_set('memory_limit', '256M');
} }
/** /**
@ -75,6 +74,8 @@ class ImportCities extends Command
*/ */
public function handle() public function handle()
{ {
$old_memory_limit = ini_get('memory_limit');
ini_set('memory_limit', '256M');
$path = storage_path('app/cities.json'); $path = storage_path('app/cities.json');
if(hash_file('sha512', $path) !== self::CHECKSUM) { if(hash_file('sha512', $path) !== self::CHECKSUM) {
@ -136,6 +137,7 @@ class ImportCities extends Command
$this->line(''); $this->line('');
$this->info('Successfully imported ' . $cityCount . ' entries!'); $this->info('Successfully imported ' . $cityCount . ' entries!');
$this->line(''); $this->line('');
ini_set('memory_limit', $old_memory_limit);
return; return;
} }

View file

@ -3,14 +3,13 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\{ use Illuminate\Support\Facades\DB;
DB, use Illuminate\Support\Facades\Storage;
Storage use App\Story;
}; use App\StoryView;
use App\{ use App\Jobs\StoryPipeline\StoryExpire;
Story, use App\Jobs\StoryPipeline\StoryRotateMedia;
StoryView use App\Services\StoryService;
};
class StoryGC extends Command class StoryGC extends Command
{ {
@ -45,61 +44,41 @@ class StoryGC extends Command
*/ */
public function handle() public function handle()
{ {
$this->directoryScan(); $this->archiveExpiredStories();
$this->deleteViews(); $this->rotateMedia();
$this->deleteStories();
} }
protected function directoryScan() protected function archiveExpiredStories()
{ {
$day = now()->day; $stories = Story::whereActive(true)
->where('created_at', '<', now()->subHours(24))
->get();
if($day != 3) { foreach($stories as $story) {
StoryExpire::dispatch($story)->onQueue('story');
}
}
protected function rotateMedia()
{
$queue = StoryService::rotateQueue();
if(!$queue || count($queue) == 0) {
return; return;
} }
$monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12); collect($queue)
->each(function($id) {
$t1 = Storage::directories('public/_esm.t1'); $story = StoryService::getById($id);
$t2 = Storage::directories('public/_esm.t2'); if(!$story) {
StoryService::removeRotateQueue($id);
$dirs = array_merge($t1, $t2); return;
foreach($dirs as $dir) {
$hash = last(explode('/', $dir));
if($hash != $monthHash) {
$this->info('Found directory to delete: ' . $dir);
$this->deleteDirectory($dir);
} }
if($story->created_at->gt(now()->subMinutes(20))) {
return;
} }
} StoryRotateMedia::dispatch($story)->onQueue('story');
StoryService::removeRotateQueue($id);
protected function deleteDirectory($path)
{
Storage::deleteDirectory($path);
}
protected function deleteViews()
{
StoryView::where('created_at', '<', now()->subMinutes(1441))->delete();
}
protected function deleteStories()
{
$stories = Story::where('created_at', '<', now()->subMinutes(1441))->take(50)->get();
if($stories->count() == 0) {
exit;
}
foreach($stories as $story) {
if(Storage::exists($story->path) == true) {
Storage::delete($story->path);
}
DB::transaction(function() use($story) {
StoryView::whereStoryId($story->id)->delete();
$story->delete();
}); });
} }
} }
}

View file

@ -12,7 +12,7 @@ class UserCreate extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'user:create'; protected $signature = 'user:create {--name=} {--username=} {--email=} {--password=} {--is_admin=0} {--confirm_email=0}';
/** /**
* The console command description. * The console command description.
@ -40,6 +40,26 @@ class UserCreate extends Command
{ {
$this->info('Creating a new user...'); $this->info('Creating a new user...');
$o = $this->options();
if( $o['name'] &&
$o['username'] &&
$o['email'] &&
$o['password']
) {
$user = new User;
$user->username = $o['username'];
$user->name = $o['name'];
$user->email = $o['email'];
$user->password = bcrypt($o['password']);
$user->is_admin = (bool) $o['is_admin'];
$user->email_verified_at = (bool) $o['confirm_email'] ? now() : null;
$user->save();
$this->info('Successfully created user!');
return;
}
$name = $this->ask('Name'); $name = $this->ask('Name');
$username = $this->ask('Username'); $username = $this->ask('Username');

View file

@ -31,6 +31,7 @@ class Kernel extends ConsoleKernel
$schedule->command('story:gc')->everyFiveMinutes(); $schedule->command('story:gc')->everyFiveMinutes();
$schedule->command('gc:failedjobs')->dailyAt(3); $schedule->command('gc:failedjobs')->dailyAt(3);
$schedule->command('gc:passwordreset')->dailyAt('09:41'); $schedule->command('gc:passwordreset')->dailyAt('09:41');
$schedule->command('gc:sessions')->twiceDaily(13, 23);
} }
/** /**

View file

@ -4,6 +4,7 @@ namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable; use Throwable;
use League\OAuth2\Server\Exception\OAuthServerException;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
@ -13,7 +14,10 @@ class Handler extends ExceptionHandler
* @var array * @var array
*/ */
protected $dontReport = [ protected $dontReport = [
// OAuthServerException::class,
\Zttp\ConnectionException::class,
\GuzzleHttp\Exception\ConnectException::class,
\Illuminate\Http\Client\ConnectionException::class
]; ];
/** /**
@ -38,6 +42,22 @@ class Handler extends ExceptionHandler
parent::report($exception); parent::report($exception);
} }
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (\BadMethodCallException $e) {
return app()->environment() !== 'production';
});
$this->reportable(function (\Illuminate\Http\Client\ConnectionException $e) {
return app()->environment() !== 'production';
});
}
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *
@ -48,6 +68,11 @@ class Handler extends ExceptionHandler
*/ */
public function render($request, Throwable $exception) public function render($request, Throwable $exception)
{ {
if ($request->wantsJson())
return response()->json(
['error' => $exception->getMessage()],
method_exists($exception, 'getStatusCode') ? $exception->getStatusCode() : 500
);
return parent::render($request, $exception); return parent::render($request, $exception);
} }
} }

View file

@ -0,0 +1,19 @@
<?php
namespace App;
use App\Services\SnowflakeService;
trait HasSnowflakePrimary
{
public static function bootHasSnowflakePrimary()
{
static::saving(function ($model) {
if (is_null($model->getKey())) {
$keyName = $model->getKeyName();
$id = SnowflakeService::next();
$model->setAttribute($keyName, $id);
}
});
}
}

View file

@ -22,6 +22,13 @@ use App\{
User, User,
UserFilter UserFilter
}; };
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Transformer\Api\Mastodon\v1\AccountTransformer;
use App\Services\AccountService;
use App\Services\UserFilterService;
use App\Services\RelationshipService;
class AccountController extends Controller class AccountController extends Controller
{ {
@ -30,6 +37,8 @@ class AccountController extends Controller
'user.block', 'user.block',
]; ];
const FILTER_LIMIT = 'You cannot block or mute more than 100 accounts';
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
@ -67,7 +76,10 @@ class AccountController extends Controller
public function verifyEmail(Request $request) public function verifyEmail(Request $request)
{ {
return view('account.verify_email'); $recentSent = EmailVerification::whereUserId(Auth::id())
->whereDate('created_at', '>', now()->subHours(12))->count();
return view('account.verify_email', compact('recentSent'));
} }
public function sendVerifyEmail(Request $request) public function sendVerifyEmail(Request $request)
@ -136,6 +148,12 @@ class AccountController extends Controller
]); ]);
$user = Auth::user()->profile; $user = Auth::user()->profile;
$count = UserFilterService::muteCount($user->id);
abort_if($count >= 100, 422, self::FILTER_LIMIT);
if($count == 0) {
$filterCount = UserFilter::whereUserId($user->id)->count();
abort_if($filterCount >= 100, 422, self::FILTER_LIMIT);
}
$type = $request->input('type'); $type = $request->input('type');
$item = $request->input('item'); $item = $request->input('item');
$action = $type . '.mute'; $action = $type . '.mute';
@ -167,6 +185,7 @@ class AccountController extends Controller
Cache::forget("user:filter:list:$pid"); Cache::forget("user:filter:list:$pid");
Cache::forget("feature:discover:posts:$pid"); Cache::forget("feature:discover:posts:$pid");
Cache::forget("api:local:exp:rec:$pid"); Cache::forget("api:local:exp:rec:$pid");
RelationshipService::refresh($pid, $profile->id);
return redirect()->back(); return redirect()->back();
} }
@ -217,6 +236,7 @@ class AccountController extends Controller
Cache::forget("user:filter:list:$pid"); Cache::forget("user:filter:list:$pid");
Cache::forget("feature:discover:posts:$pid"); Cache::forget("feature:discover:posts:$pid");
Cache::forget("api:local:exp:rec:$pid"); Cache::forget("api:local:exp:rec:$pid");
RelationshipService::refresh($pid, $profile->id);
if($request->wantsJson()) { if($request->wantsJson()) {
return response()->json([200]); return response()->json([200]);
@ -233,6 +253,12 @@ class AccountController extends Controller
]); ]);
$user = Auth::user()->profile; $user = Auth::user()->profile;
$count = UserFilterService::blockCount($user->id);
abort_if($count >= 100, 422, self::FILTER_LIMIT);
if($count == 0) {
$filterCount = UserFilter::whereUserId($user->id)->count();
abort_if($filterCount >= 100, 422, self::FILTER_LIMIT);
}
$type = $request->input('type'); $type = $request->input('type');
$item = $request->input('item'); $item = $request->input('item');
$action = $type.'.block'; $action = $type.'.block';
@ -243,7 +269,7 @@ class AccountController extends Controller
switch ($type) { switch ($type) {
case 'user': case 'user':
$profile = Profile::findOrFail($item); $profile = Profile::findOrFail($item);
if ($profile->id == $user->id || $profile->user->is_admin == true) { if ($profile->id == $user->id || ($profile->user && $profile->user->is_admin == true)) {
return abort(403); return abort(403);
} }
$class = get_class($profile); $class = get_class($profile);
@ -265,6 +291,7 @@ class AccountController extends Controller
$pid = $user->id; $pid = $user->id;
Cache::forget("user:filter:list:$pid"); Cache::forget("user:filter:list:$pid");
Cache::forget("api:local:exp:rec:$pid"); Cache::forget("api:local:exp:rec:$pid");
RelationshipService::refresh($pid, $profile->id);
return redirect()->back(); return redirect()->back();
} }
@ -315,6 +342,7 @@ class AccountController extends Controller
Cache::forget("user:filter:list:$pid"); Cache::forget("user:filter:list:$pid");
Cache::forget("feature:discover:posts:$pid"); Cache::forget("feature:discover:posts:$pid");
Cache::forget("api:local:exp:rec:$pid"); Cache::forget("api:local:exp:rec:$pid");
RelationshipService::refresh($pid, $profile->id);
return redirect()->back(); return redirect()->back();
} }
@ -487,4 +515,82 @@ class AccountController extends Controller
public function accountRestored(Request $request) public function accountRestored(Request $request)
{ {
} }
public function accountMutes(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'limit' => 'nullable|integer|min:1|max:40'
]);
$user = $request->user();
$limit = $request->input('limit') ?? 40;
$mutes = UserFilter::whereUserId($user->profile_id)
->whereFilterableType('App\Profile')
->whereFilterType('mute')
->simplePaginate($limit)
->pluck('filterable_id');
$accounts = Profile::find($mutes);
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Collection($accounts, new AccountTransformer());
$res = $fractal->createData($resource)->toArray();
$url = $request->url();
$page = $request->input('page', 1);
$next = $page < 40 ? $page + 1 : 40;
$prev = $page > 1 ? $page - 1 : 1;
$links = '<'.$url.'?page='.$next.'&limit='.$limit.'>; rel="next", <'.$url.'?page='.$prev.'&limit='.$limit.'>; rel="prev"';
return response()->json($res, 200, ['Link' => $links]);
}
public function accountBlocks(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'limit' => 'nullable|integer|min:1|max:40',
'page' => 'nullable|integer|min:1|max:10'
]);
$user = $request->user();
$limit = $request->input('limit') ?? 40;
$blocked = UserFilter::select('filterable_id','filterable_type','filter_type','user_id')
->whereUserId($user->profile_id)
->whereFilterableType('App\Profile')
->whereFilterType('block')
->simplePaginate($limit)
->pluck('filterable_id');
$profiles = Profile::findOrFail($blocked);
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
$res = $fractal->createData($resource)->toArray();
$url = $request->url();
$page = $request->input('page', 1);
$next = $page < 40 ? $page + 1 : 40;
$prev = $page > 1 ? $page - 1 : 1;
$links = '<'.$url.'?page='.$next.'&limit='.$limit.'>; rel="next", <'.$url.'?page='.$prev.'&limit='.$limit.'>; rel="prev"';
return response()->json($res, 200, ['Link' => $links]);
}
public function accountBlocksV2(Request $request)
{
return response()->json(UserFilterService::blocks($request->user()->profile_id), 200, [], JSON_UNESCAPED_SLASHES);
}
public function accountMutesV2(Request $request)
{
return response()->json(UserFilterService::mutes($request->user()->profile_id), 200, [], JSON_UNESCAPED_SLASHES);
}
public function accountFiltersV2(Request $request)
{
return response()->json(UserFilterService::filters($request->user()->profile_id), 200, [], JSON_UNESCAPED_SLASHES);
}
} }

View file

@ -14,29 +14,58 @@ trait AdminInstanceController
public function instances(Request $request) public function instances(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'filter' => [ 'filter' => [
'nullable', 'nullable',
'string', 'string',
'min:1', 'min:1',
'max:20', 'max:20',
Rule::in(['autocw', 'unlisted', 'banned']) Rule::in([
'cw',
'unlisted',
'banned',
// 'popular',
'new',
'all'
])
], ],
]); ]);
if($request->has('filter') && $request->filled('filter')) { if($request->has('q') && $request->filled('q')) {
$instances = Instance::where('domain', 'like', '%' . $request->input('q') . '%')->simplePaginate(10);
} else if($request->has('filter') && $request->filled('filter')) {
switch ($request->filter) { switch ($request->filter) {
case 'autocw': case 'cw':
$instances = Instance::whereAutoCw(true)->orderByDesc('id')->paginate(5); $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->whereAutoCw(true)->orderByDesc('id')->simplePaginate(10);
break; break;
case 'unlisted': case 'unlisted':
$instances = Instance::whereUnlisted(true)->orderByDesc('id')->paginate(5); $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->whereUnlisted(true)->orderByDesc('id')->simplePaginate(10);
break; break;
case 'banned': case 'banned':
$instances = Instance::whereBanned(true)->orderByDesc('id')->paginate(5); $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->whereBanned(true)->orderByDesc('id')->simplePaginate(10);
break;
case 'new':
$instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->latest()->simplePaginate(10);
break;
// case 'popular':
// $popular = Profile::selectRaw('*, count(domain) as count')
// ->whereNotNull('domain')
// ->groupBy('domain')
// ->orderByDesc('count')
// ->take(10)
// ->get()
// ->pluck('domain')
// ->toArray();
// $instances = Instance::whereIn('domain', $popular)->simplePaginate(10);
// break;
default:
$instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->orderByDesc('id')->simplePaginate(10);
break; break;
} }
} else { } else {
$instances = Instance::orderByDesc('id')->paginate(5); $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->orderByDesc('id')->simplePaginate(10);
} }
return view('admin.instances.home', compact('instances')); return view('admin.instances.home', compact('instances'));
} }
@ -97,6 +126,10 @@ trait AdminInstanceController
break; break;
} }
Cache::forget('instances:banned:domains');
Cache::forget('instances:unlisted:domains');
Cache::forget('instances:auto_cw:domains');
return response()->json([]); return response()->json([]);
} }
} }

View file

@ -27,6 +27,7 @@ trait AdminMediaController
], ],
'search' => 'nullable|string|min:1|max:20' 'search' => 'nullable|string|min:1|max:20'
]); ]);
if($request->filled('search')) { if($request->filled('search')) {
$profiles = Profile::where('username', 'like', '%'.$request->input('search').'%')->pluck('id')->toArray(); $profiles = Profile::where('username', 'like', '%'.$request->input('search').'%')->pluck('id')->toArray();
$media = Media::whereHas('status') $media = Media::whereHas('status')
@ -42,7 +43,8 @@ trait AdminMediaController
$media = MediaBlocklist::latest()->paginate(12); $media = MediaBlocklist::latest()->paginate(12);
return view('admin.media.home', compact('media')); return view('admin.media.home', compact('media'));
} }
$media = Media::whereHas('status')->with('status')->orderby('id', 'desc')->paginate(12);
$media = Media::whereNull('remote_url')->orderby('id', 'desc')->simplePaginate(12);
return view('admin.media.home', compact('media')); return view('admin.media.home', compact('media'));
} }

View file

@ -3,12 +3,372 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use Cache; use Cache;
use App\Report;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use App\Services\AccountService;
use App\Services\StatusService;
use App\{
AccountInterstitial,
Contact,
Hashtag,
Newsroom,
OauthClient,
Profile,
Report,
Status,
Story,
User
};
use Illuminate\Validation\Rule;
use App\Services\StoryService;
trait AdminReportController trait AdminReportController
{ {
public function reports(Request $request)
{
$filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
$page = $request->input('page') ?? 1;
$ai = Cache::remember('admin-dash:reports:ai-count', 3600, function() {
return AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count();
});
$spam = Cache::remember('admin-dash:reports:spam-count', 3600, function() {
return AccountInterstitial::whereType('post.autospam')->whereNull('appeal_handled_at')->count();
});
$mailVerifications = Redis::scard('email:manual');
if($filter == 'open' && $page == 1) {
$reports = Cache::remember('admin-dash:reports:list-cache', 300, function() use($page, $filter) {
return Report::whereHas('status')
->whereHas('reportedUser')
->whereHas('reporter')
->orderBy('created_at','desc')
->when($filter, function($q, $filter) {
return $filter == 'open' ?
$q->whereNull('admin_seen') :
$q->whereNotNull('admin_seen');
})
->paginate(6);
});
} else {
$reports = Report::whereHas('status')
->whereHas('reportedUser')
->whereHas('reporter')
->orderBy('created_at','desc')
->when($filter, function($q, $filter) {
return $filter == 'open' ?
$q->whereNull('admin_seen') :
$q->whereNotNull('admin_seen');
})
->paginate(6);
}
return view('admin.reports.home', compact('reports', 'ai', 'spam', 'mailVerifications'));
}
public function showReport(Request $request, $id)
{
$report = Report::findOrFail($id);
return view('admin.reports.show', compact('report'));
}
public function appeals(Request $request)
{
$appeals = AccountInterstitial::whereNotNull('appeal_requested_at')
->whereNull('appeal_handled_at')
->latest()
->paginate(6);
return view('admin.reports.appeals', compact('appeals'));
}
public function showAppeal(Request $request, $id)
{
$appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
->whereNull('appeal_handled_at')
->findOrFail($id);
$meta = json_decode($appeal->meta);
return view('admin.reports.show_appeal', compact('appeal', 'meta'));
}
public function spam(Request $request)
{
$this->validate($request, [
'tab' => 'sometimes|in:home,not-spam,spam,settings,custom,exemptions'
]);
$tab = $request->input('tab', 'home');
$openCount = Cache::remember('admin-dash:reports:spam-count', 3600, function() {
return AccountInterstitial::whereType('post.autospam')
->whereNull('appeal_handled_at')
->count();
});
$monthlyCount = Cache::remember('admin-dash:reports:spam-count:30d', 43200, function() {
return AccountInterstitial::whereType('post.autospam')
->where('created_at', '>', now()->subMonth())
->count();
});
$totalCount = Cache::remember('admin-dash:reports:spam-count:total', 43200, function() {
return AccountInterstitial::whereType('post.autospam')->count();
});
$uncategorized = Cache::remember('admin-dash:reports:spam-sync', 3600, function() {
return AccountInterstitial::whereType('post.autospam')
->whereIsSpam(null)
->whereNotNull('appeal_handled_at')
->exists();
});
$avg = Cache::remember('admin-dash:reports:spam-count:avg', 43200, function() {
if(config('database.default') != 'mysql') {
return 0;
}
return AccountInterstitial::selectRaw('*, count(id) as counter')
->whereType('post.autospam')
->groupBy('user_id')
->get()
->avg('counter');
});
$avgOpen = Cache::remember('admin-dash:reports:spam-count:avgopen', 43200, function() {
if(config('database.default') != 'mysql') {
return "0";
}
$seconds = AccountInterstitial::selectRaw('DATE(created_at) AS start_date, AVG(TIME_TO_SEC(TIMEDIFF(appeal_handled_at, created_at))) AS timediff')->whereType('post.autospam')->whereNotNull('appeal_handled_at')->where('created_at', '>', now()->subMonth())->get();
if(!$seconds) {
return "0";
}
$mins = floor($seconds->avg('timediff') / 60);
if($mins < 60) {
return $mins . ' min(s)';
}
if($mins < 2880) {
return floor($mins / 60) . ' hour(s)';
}
return floor($mins / 60 / 24) . ' day(s)';
});
$avgCount = $totalCount && $avg ? floor($totalCount / $avg) : "0";
if(in_array($tab, ['home', 'spam', 'not-spam'])) {
$appeals = AccountInterstitial::whereType('post.autospam')
->when($tab, function($q, $tab) {
switch($tab) {
case 'home':
return $q->whereNull('appeal_handled_at');
break;
case 'spam':
return $q->whereIsSpam(true);
break;
case 'not-spam':
return $q->whereIsSpam(false);
break;
}
})
->latest()
->paginate(6);
if($tab !== 'home') {
$appeals = $appeals->appends(['tab' => $tab]);
}
} else {
$appeals = new class {
public function count() {
return 0;
}
public function render() {
return;
}
};
}
return view('admin.reports.spam', compact('tab', 'appeals', 'openCount', 'monthlyCount', 'totalCount', 'avgCount', 'avgOpen', 'uncategorized'));
}
public function showSpam(Request $request, $id)
{
$appeal = AccountInterstitial::whereType('post.autospam')
->findOrFail($id);
$meta = json_decode($appeal->meta);
return view('admin.reports.show_spam', compact('appeal', 'meta'));
}
public function fixUncategorizedSpam(Request $request)
{
if(Cache::get('admin-dash:reports:spam-sync-active')) {
return redirect('/i/admin/reports/autospam');
}
Cache::put('admin-dash:reports:spam-sync-active', 1, 900);
AccountInterstitial::chunk(500, function($reports) {
foreach($reports as $report) {
if($report->item_type != 'App\Status') {
continue;
}
if($report->type != 'post.autospam') {
continue;
}
if($report->is_spam != null) {
continue;
}
$status = StatusService::get($report->item_id, false);
if(!$status) {
return;
}
$scope = $status['visibility'];
$report->is_spam = $scope == 'unlisted';
$report->in_violation = $report->is_spam;
$report->severity_index = 1;
$report->save();
}
});
Cache::forget('admin-dash:reports:spam-sync');
return redirect('/i/admin/reports/autospam');
}
public function updateSpam(Request $request, $id)
{
$this->validate($request, [
'action' => 'required|in:dismiss,approve,dismiss-all,approve-all'
]);
$action = $request->input('action');
$appeal = AccountInterstitial::whereType('post.autospam')
->whereNull('appeal_handled_at')
->findOrFail($id);
$meta = json_decode($appeal->meta);
$res = ['status' => 'success'];
$now = now();
Cache::forget('admin-dash:reports:spam-count:total');
Cache::forget('admin-dash:reports:spam-count:30d');
if($action == 'dismiss') {
$appeal->is_spam = true;
$appeal->appeal_handled_at = $now;
$appeal->save();
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
Cache::forget('admin-dash:reports:spam-count');
return $res;
}
if($action == 'dismiss-all') {
AccountInterstitial::whereType('post.autospam')
->whereItemType('App\Status')
->whereNull('appeal_handled_at')
->whereUserId($appeal->user_id)
->update(['appeal_handled_at' => $now, 'is_spam' => true]);
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
Cache::forget('admin-dash:reports:spam-count');
return $res;
}
if($action == 'approve-all') {
AccountInterstitial::whereType('post.autospam')
->whereItemType('App\Status')
->whereNull('appeal_handled_at')
->whereUserId($appeal->user_id)
->get()
->each(function($report) use($meta) {
$report->is_spam = false;
$report->appeal_handled_at = now();
$report->save();
$status = Status::find($report->item_id);
if($status) {
$status->is_nsfw = $meta->is_nsfw;
$status->scope = 'public';
$status->visibility = 'public';
$status->save();
StatusService::del($status->id, true);
}
});
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
Cache::forget('admin-dash:reports:spam-count');
return $res;
}
$status = $appeal->status;
$status->is_nsfw = $meta->is_nsfw;
$status->scope = 'public';
$status->visibility = 'public';
$status->save();
$appeal->is_spam = false;
$appeal->appeal_handled_at = now();
$appeal->save();
StatusService::del($status->id);
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
Cache::forget('admin-dash:reports:spam-count');
return $res;
}
public function updateAppeal(Request $request, $id)
{
$this->validate($request, [
'action' => 'required|in:dismiss,approve'
]);
$action = $request->input('action');
$appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
->whereNull('appeal_handled_at')
->findOrFail($id);
if($action == 'dismiss') {
$appeal->appeal_handled_at = now();
$appeal->save();
Cache::forget('admin-dash:reports:ai-count');
return redirect('/i/admin/reports/appeals');
}
switch ($appeal->type) {
case 'post.cw':
$status = $appeal->status;
$status->is_nsfw = false;
$status->save();
break;
case 'post.unlist':
$status = $appeal->status;
$status->scope = 'public';
$status->visibility = 'public';
$status->save();
break;
default:
# code...
break;
}
$appeal->appeal_handled_at = now();
$appeal->save();
StatusService::del($status->id, true);
Cache::forget('admin-dash:reports:ai-count');
return redirect('/i/admin/reports/appeals');
}
public function updateReport(Request $request, $id) public function updateReport(Request $request, $id)
{ {
$this->validate($request, [ $this->validate($request, [
@ -33,6 +393,7 @@ trait AdminReportController
$report = Report::findOrFail($id); $report = Report::findOrFail($id);
$this->handleReportAction($report, $action); $this->handleReportAction($report, $action);
Cache::forget('admin-dash:reports:list-cache');
return response()->json(['msg'=> 'Success']); return response()->json(['msg'=> 'Success']);
} }
@ -52,17 +413,20 @@ trait AdminReportController
$item->is_nsfw = true; $item->is_nsfw = true;
$item->save(); $item->save();
$report->nsfw = true; $report->nsfw = true;
StatusService::del($item->id, true);
break; break;
case 'unlist': case 'unlist':
$item->visibility = 'unlisted'; $item->visibility = 'unlisted';
$item->save(); $item->save();
Cache::forget('profiles:private'); Cache::forget('profiles:private');
StatusService::del($item->id, true);
break; break;
case 'delete': case 'delete':
// Todo: fire delete job // Todo: fire delete job
$report->admin_seen = null; $report->admin_seen = null;
StatusService::del($item->id, true);
break; break;
case 'shadowban': case 'shadowban':
@ -115,4 +479,55 @@ trait AdminReportController
]; ];
return response()->json($res); return response()->json($res);
} }
public function reportMailVerifications(Request $request)
{
$ids = Redis::smembers('email:manual');
$ignored = Redis::smembers('email:manual-ignored');
$reports = [];
if($ids) {
$reports = collect($ids)
->filter(function($id) use($ignored) {
return !in_array($id, $ignored);
})
->map(function($id) {
$account = AccountService::get($id);
$user = User::whereProfileId($id)->first();
if(!$user) {
return [];
}
$account['email'] = $user->email;
return $account;
})
->filter(function($res) {
return $res && isset($res['id']);
})
->values();
}
return view('admin.reports.mail_verification', compact('reports', 'ignored'));
}
public function reportMailVerifyIgnore(Request $request)
{
$id = $request->input('id');
Redis::sadd('email:manual-ignored', $id);
return redirect('/i/admin/reports');
}
public function reportMailVerifyApprove(Request $request)
{
$id = $request->input('id');
$user = User::whereProfileId($id)->firstOrFail();
Redis::srem('email:manual', $id);
Redis::srem('email:manual-ignored', $id);
$user->email_verified_at = now();
$user->save();
return redirect('/i/admin/reports');
}
public function reportMailVerifyClearIgnored(Request $request)
{
Redis::del('email:manual-ignored');
return [200];
}
} }

View file

@ -6,14 +6,192 @@ use Artisan, Cache, DB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Carbon\Carbon; use Carbon\Carbon;
use App\{Comment, Like, Media, Page, Profile, Report, Status, User}; use App\{Comment, Like, Media, Page, Profile, Report, Status, User};
use App\Models\InstanceActor;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Util\Lexer\PrettyNumber; use App\Util\Lexer\PrettyNumber;
use App\Models\ConfigCache;
use App\Services\ConfigCacheService;
use App\Util\Site\Config;
trait AdminSettingsController trait AdminSettingsController
{ {
public function settings(Request $request) public function settings(Request $request)
{ {
return view('admin.settings.home'); $cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage');
$cloud_disk = config('filesystems.cloud');
$cloud_ready = !empty(config('filesystems.disks.' . $cloud_disk . '.key')) && !empty(config('filesystems.disks.' . $cloud_disk . '.secret'));
$types = explode(',', ConfigCacheService::get('pixelfed.media_types'));
$rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : null;
$jpeg = in_array('image/jpg', $types) ? true : in_array('image/jpeg', $types);
$png = in_array('image/png', $types);
$gif = in_array('image/gif', $types);
$mp4 = in_array('video/mp4', $types);
$webp = in_array('image/webp', $types);
// $system = [
// 'permissions' => is_writable(base_path('storage')) && is_writable(base_path('bootstrap')),
// 'max_upload_size' => ini_get('post_max_size'),
// 'image_driver' => config('image.driver'),
// 'image_driver_loaded' => extension_loaded(config('image.driver'))
// ];
return view('admin.settings.home', compact(
'jpeg',
'png',
'gif',
'mp4',
'webp',
'rules',
'cloud_storage',
'cloud_disk',
'cloud_ready',
// 'system'
));
}
public function settingsHomeStore(Request $request)
{
$this->validate($request, [
'name' => 'nullable|string',
'short_description' => 'nullable',
'long_description' => 'nullable',
'max_photo_size' => 'nullable|integer|min:1',
'max_album_length' => 'nullable|integer|min:1|max:100',
'image_quality' => 'nullable|integer|min:1|max:100',
'type_jpeg' => 'nullable',
'type_png' => 'nullable',
'type_gif' => 'nullable',
'type_mp4' => 'nullable',
'type_webp' => 'nullable',
]);
if($request->filled('rule_delete')) {
$index = (int) $request->input('rule_delete');
$rules = ConfigCacheService::get('app.rules');
$json = json_decode($rules, true);
if(!$rules || empty($json)) {
return;
}
unset($json[$index]);
$json = json_encode(array_values($json));
ConfigCacheService::put('app.rules', $json);
return 200;
}
$media_types = explode(',', config_cache('pixelfed.media_types'));
$media_types_original = $media_types;
$mimes = [
'type_jpeg' => 'image/jpeg',
'type_png' => 'image/png',
'type_gif' => 'image/gif',
'type_mp4' => 'video/mp4',
'type_webp' => 'image/webp',
];
foreach ($mimes as $key => $value) {
if($request->input($key) == 'on') {
if(!in_array($value, $media_types)) {
array_push($media_types, $value);
}
} else {
$media_types = array_diff($media_types, [$value]);
}
}
if($media_types !== $media_types_original) {
ConfigCacheService::put('pixelfed.media_types', implode(',', array_unique($media_types)));
}
$keys = [
'name' => 'app.name',
'short_description' => 'app.short_description',
'long_description' => 'app.description',
'max_photo_size' => 'pixelfed.max_photo_size',
'max_album_length' => 'pixelfed.max_album_length',
'image_quality' => 'pixelfed.image_quality',
'account_limit' => 'pixelfed.max_account_size',
'custom_css' => 'uikit.custom.css',
'custom_js' => 'uikit.custom.js',
'about_title' => 'about.title'
];
foreach ($keys as $key => $value) {
$cc = ConfigCache::whereK($value)->first();
$val = $request->input($key);
if($cc && $cc->v != $val) {
ConfigCacheService::put($value, $val);
}
}
$bools = [
'activitypub' => 'federation.activitypub.enabled',
'open_registration' => 'pixelfed.open_registration',
'mobile_apis' => 'pixelfed.oauth_enabled',
'stories' => 'instance.stories.enabled',
'ig_import' => 'pixelfed.import.instagram.enabled',
'spam_detection' => 'pixelfed.bouncer.enabled',
'require_email_verification' => 'pixelfed.enforce_email_verification',
'enforce_account_limit' => 'pixelfed.enforce_account_limit',
'show_custom_css' => 'uikit.show_custom.css',
'show_custom_js' => 'uikit.show_custom.js',
'cloud_storage' => 'pixelfed.cloud_storage',
'account_autofollow' => 'account.autofollow'
];
foreach ($bools as $key => $value) {
$active = $request->input($key) == 'on';
if($key == 'activitypub' && $active && !InstanceActor::exists()) {
Artisan::call('instance:actor');
}
if( $key == 'mobile_apis' &&
$active &&
!file_exists(storage_path('oauth-public.key')) &&
!file_exists(storage_path('oauth-private.key'))
) {
Artisan::call('passport:keys');
Artisan::call('route:cache');
}
if(config_cache($value) !== $active) {
ConfigCacheService::put($value, (bool) $active);
}
}
if($request->filled('new_rule')) {
$rules = ConfigCacheService::get('app.rules');
$val = $request->input('new_rule');
if(!$rules) {
ConfigCacheService::put('app.rules', json_encode([$val]));
} else {
$json = json_decode($rules, true);
$json[] = $val;
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
}
Cache::forget('api:v1:instance-data:rules');
Cache::forget('api:v1:instance-data-response');
}
if($request->filled('account_autofollow_usernames')) {
$usernames = explode(',', $request->input('account_autofollow_usernames'));
$names = [];
foreach($usernames as $n) {
$p = Profile::whereUsername($n)->first();
if(!$p) {
continue;
}
array_push($names, $p->username);
}
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
}
Cache::forget(Config::CACHE_KEY);
return redirect('/i/admin/settings')->with('status', 'Successfully updated settings!');
} }
public function settingsBackups(Request $request) public function settingsBackups(Request $request)
@ -23,51 +201,6 @@ trait AdminSettingsController
return view('admin.settings.backups', compact('files')); return view('admin.settings.backups', compact('files'));
} }
public function settingsConfig(Request $request)
{
$editor = config('pixelfed.admin.env_editor');
$config = !$editor ? false : file_get_contents(base_path('.env'));
$backup = !$editor ? false : (is_file(base_path('.env.backup')) ? file_get_contents(base_path('.env.backup')) : false);
return view('admin.settings.config', compact('editor', 'config', 'backup'));
}
public function settingsConfigStore(Request $request)
{
if(config('pixelfed.admin.env_editor') !== true) {
abort(400);
}
$res = $request->input('res');
$old = file_get_contents(app()->environmentFilePath());
if(empty($old) || $old != $res) {
$oldFile = fopen(app()->environmentFilePath().'.backup', 'w');
fwrite($oldFile, $old);
fclose($oldFile);
}
$file = fopen(app()->environmentFilePath(), 'w');
fwrite($file, $res);
fclose($file);
Artisan::call('config:cache');
return ['msg' => 200];
}
public function settingsConfigRestore(Request $request)
{
if(config('pixelfed.admin.env_editor') !== true) {
abort(400);
}
$res = file_get_contents(app()->environmentFilePath().'.backup');
if(empty($res)) {
abort(400, 'No backup exists.');
}
$file = fopen(app()->environmentFilePath(), 'w');
fwrite($file, $res);
fclose($file);
Artisan::call('config:cache');
return ['msg' => 200];
}
public function settingsMaintenance(Request $request) public function settingsMaintenance(Request $request)
{ {
return view('admin.settings.maintenance'); return view('admin.settings.maintenance');
@ -84,15 +217,6 @@ trait AdminSettingsController
return view('admin.settings.features'); return view('admin.settings.features');
} }
public function settingsHomeStore(Request $request)
{
$this->validate($request, [
'APP_NAME' => 'required|string',
]);
// Artisan::call('config:clear');
return redirect()->back();
}
public function settingsPages(Request $request) public function settingsPages(Request $request)
{ {
$pages = Page::orderByDesc('updated_at')->paginate(10); $pages = Page::orderByDesc('updated_at')->paginate(10);

View file

@ -16,14 +16,27 @@ trait AdminUserController
{ {
public function users(Request $request) public function users(Request $request)
{ {
$search = $request->has('a') && $request->query('a') == 'search' ? $request->query('q') : null;
$col = $request->query('col') ?? 'id'; $col = $request->query('col') ?? 'id';
$dir = $request->query('dir') ?? 'desc'; $dir = $request->query('dir') ?? 'desc';
$users = User::select('id', 'username', 'status') $offset = $request->has('page') ? $request->input('page') : 0;
->withCount('statuses') $pagination = [
'prev' => $offset > 0 ? $offset - 1 : null,
'next' => $offset + 1,
'query' => $search ? '&a=search&q=' . $search : null
];
$users = User::select('id', 'username', 'status', 'profile_id')
->orderBy($col, $dir) ->orderBy($col, $dir)
->simplePaginate(10); ->when($search, function($q, $search) {
return $q->where('username', 'like', "%{$search}%");
})
->when($offset, function($q, $offset) {
return $q->offset(($offset * 10));
})
->limit(10)
->get();
return view('admin.users.home', compact('users')); return view('admin.users.home', compact('users', 'pagination'));
} }
public function userShow(Request $request, $id) public function userShow(Request $request, $id)

View file

@ -11,30 +11,39 @@ use App\{
Profile, Profile,
Report, Report,
Status, Status,
Story,
User User
}; };
use DB, Cache; use DB, Cache, Storage;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use App\Http\Controllers\Admin\{ use App\Http\Controllers\Admin\{
AdminDiscoverController, AdminDiscoverController,
AdminInstanceController, AdminInstanceController,
AdminReportController, AdminReportController,
// AdminGroupsController,
AdminMediaController, AdminMediaController,
AdminSettingsController, AdminSettingsController,
// AdminStorageController,
AdminSupportController, AdminSupportController,
AdminUserController AdminUserController
}; };
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use App\Services\AdminStatsService; use App\Services\AdminStatsService;
use App\Services\StatusService;
use App\Services\StoryService;
use App\Models\CustomEmoji;
class AdminController extends Controller class AdminController extends Controller
{ {
use AdminReportController, use AdminReportController,
AdminDiscoverController, AdminDiscoverController,
// AdminGroupsController,
AdminMediaController, AdminMediaController,
AdminSettingsController, AdminSettingsController,
AdminInstanceController, AdminInstanceController,
// AdminStorageController,
AdminUserController; AdminUserController;
public function __construct() public function __construct()
@ -52,9 +61,15 @@ class AdminController extends Controller
public function statuses(Request $request) public function statuses(Request $request)
{ {
$statuses = Status::orderBy('id', 'desc')->simplePaginate(10); $statuses = Status::orderBy('id', 'desc')->cursorPaginate(10);
$data = $statuses->map(function($status) {
return view('admin.statuses.home', compact('statuses')); return StatusService::get($status->id, false);
})
->filter(function($s) {
return $s;
})
->toArray();
return view('admin.statuses.home', compact('statuses', 'data'));
} }
public function showStatus(Request $request, $id) public function showStatus(Request $request, $id)
@ -64,139 +79,6 @@ class AdminController extends Controller
return view('admin.statuses.show', compact('status')); return view('admin.statuses.show', compact('status'));
} }
public function reports(Request $request)
{
$this->validate($request, [
'filter' => 'nullable|string|in:all,open,closed'
]);
$filter = $request->input('filter');
$reports = Report::orderBy('created_at','desc')
->when($filter, function($q, $filter) {
return $filter == 'open' ?
$q->whereNull('admin_seen') :
$q->whereNotNull('admin_seen');
})
->paginate(4);
return view('admin.reports.home', compact('reports'));
}
public function showReport(Request $request, $id)
{
$report = Report::findOrFail($id);
return view('admin.reports.show', compact('report'));
}
public function appeals(Request $request)
{
$appeals = AccountInterstitial::whereNotNull('appeal_requested_at')
->whereNull('appeal_handled_at')
->latest()
->paginate(6);
return view('admin.reports.appeals', compact('appeals'));
}
public function showAppeal(Request $request, $id)
{
$appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
->whereNull('appeal_handled_at')
->findOrFail($id);
$meta = json_decode($appeal->meta);
return view('admin.reports.show_appeal', compact('appeal', 'meta'));
}
public function spam(Request $request)
{
$appeals = AccountInterstitial::whereType('post.autospam')
->whereNull('appeal_handled_at')
->latest()
->paginate(6);
return view('admin.reports.spam', compact('appeals'));
}
public function showSpam(Request $request, $id)
{
$appeal = AccountInterstitial::whereType('post.autospam')
->whereNull('appeal_handled_at')
->findOrFail($id);
$meta = json_decode($appeal->meta);
return view('admin.reports.show_spam', compact('appeal', 'meta'));
}
public function updateSpam(Request $request, $id)
{
$this->validate($request, [
'action' => 'required|in:dismiss,approve'
]);
$action = $request->input('action');
$appeal = AccountInterstitial::whereType('post.autospam')
->whereNull('appeal_handled_at')
->findOrFail($id);
$meta = json_decode($appeal->meta);
if($action == 'dismiss') {
$appeal->appeal_handled_at = now();
$appeal->save();
return redirect('/i/admin/reports/autospam');
}
$status = $appeal->status;
$status->is_nsfw = $meta->is_nsfw;
$status->scope = 'public';
$status->visibility = 'public';
$status->save();
$appeal->appeal_handled_at = now();
$appeal->save();
return redirect('/i/admin/reports/autospam');
}
public function updateAppeal(Request $request, $id)
{
$this->validate($request, [
'action' => 'required|in:dismiss,approve'
]);
$action = $request->input('action');
$appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
->whereNull('appeal_handled_at')
->findOrFail($id);
if($action == 'dismiss') {
$appeal->appeal_handled_at = now();
$appeal->save();
return redirect('/i/admin/reports/appeals');
}
switch ($appeal->type) {
case 'post.cw':
$status = $appeal->status;
$status->is_nsfw = false;
$status->save();
break;
case 'post.unlist':
$status = $appeal->status;
$status->scope = 'public';
$status->visibility = 'public';
$status->save();
break;
default:
# code...
break;
}
$appeal->appeal_handled_at = now();
$appeal->save();
return redirect('/i/admin/reports/appeals');
}
public function profiles(Request $request) public function profiles(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
@ -434,4 +316,173 @@ class AdminController extends Controller
$redirect = $news->published_at ? $news->permalink() : $news->editUrl(); $redirect = $news->published_at ? $news->permalink() : $news->editUrl();
return redirect($redirect); return redirect($redirect);
} }
public function diagnosticsHome(Request $request)
{
return view('admin.diagnostics.home');
}
public function diagnosticsDecrypt(Request $request)
{
$this->validate($request, [
'payload' => 'required'
]);
$key = 'exception_report:';
$decrypted = decrypt($request->input('payload'));
if(!starts_with($decrypted, $key)) {
abort(403, 'Can only decrypt error diagnostics');
}
$res = [
'decrypted' => substr($decrypted, strlen($key))
];
return response()->json($res);
}
public function stories(Request $request)
{
$stories = Story::with('profile')->latest()->paginate(10);
$stats = StoryService::adminStats();
return view('admin.stories.home', compact('stories', 'stats'));
}
public function customEmojiHome(Request $request)
{
if(!config('federation.custom_emoji.enabled')) {
return view('admin.custom-emoji.not-enabled');
}
$this->validate($request, [
'sort' => 'sometimes|in:all,local,remote,duplicates,disabled,search'
]);
if($request->has('cc')) {
Cache::forget('pf:admin:custom_emoji:stats');
Cache::forget('pf:custom_emoji');
return redirect(route('admin.custom-emoji'));
}
$sort = $request->input('sort') ?? 'all';
if($sort == 'search' && empty($request->input('q'))) {
return redirect(route('admin.custom-emoji'));
}
$pg = config('database.default') == 'pgsql';
$emojis = CustomEmoji::when($sort, function($query, $sort) use($request, $pg) {
if($sort == 'all') {
if($pg) {
return $query->latest();
} else {
return $query->groupBy('shortcode')->latest();
}
} else if($sort == 'local') {
return $query->latest()->where('domain', '=', config('pixelfed.domain.app'));
} else if($sort == 'remote') {
return $query->latest()->where('domain', '!=', config('pixelfed.domain.app'));
} else if($sort == 'duplicates') {
return $query->latest()->groupBy('shortcode')->havingRaw('count(*) > 1');
} else if($sort == 'disabled') {
return $query->latest()->whereDisabled(true);
} else if($sort == 'search') {
$q = $query
->latest()
->where('shortcode', 'like', '%' . $request->input('q') . '%')
->orWhere('domain', 'like', '%' . $request->input('q') . '%');
if(!$request->has('dups')) {
$q = $q->groupBy('shortcode');
}
return $q;
}
})
->simplePaginate(10)
->withQueryString();
$stats = Cache::remember('pf:admin:custom_emoji:stats', 43200, function() use($pg) {
$res = [
'total' => CustomEmoji::count(),
'active' => CustomEmoji::whereDisabled(false)->count(),
'remote' => CustomEmoji::where('domain', '!=', config('pixelfed.domain.app'))->count(),
];
if($pg) {
$res['duplicate'] = CustomEmoji::select('shortcode')->groupBy('shortcode')->havingRaw('count(*) > 1')->count();
} else {
$res['duplicate'] = CustomEmoji::groupBy('shortcode')->havingRaw('count(*) > 1')->count();
}
return $res;
});
return view('admin.custom-emoji.home', compact('emojis', 'sort', 'stats'));
}
public function customEmojiToggleActive(Request $request, $id)
{
abort_unless(config('federation.custom_emoji.enabled'), 404);
$emoji = CustomEmoji::findOrFail($id);
$emoji->disabled = !$emoji->disabled;
$emoji->save();
$key = CustomEmoji::CACHE_KEY . str_replace(':', '', $emoji->shortcode);
Cache::forget($key);
return redirect()->back();
}
public function customEmojiAdd(Request $request)
{
abort_unless(config('federation.custom_emoji.enabled'), 404);
return view('admin.custom-emoji.add');
}
public function customEmojiStore(Request $request)
{
abort_unless(config('federation.custom_emoji.enabled'), 404);
$this->validate($request, [
'shortcode' => [
'required',
'min:3',
'max:80',
'starts_with::',
'ends_with::',
Rule::unique('custom_emoji')->where(function ($query) use($request) {
return $query->whereDomain(config('pixelfed.domain.app'))
->whereShortcode($request->input('shortcode'));
})
],
'emoji' => 'required|file|mimes:jpg,png|max:' . (config('federation.custom_emoji.max_size') / 1000)
]);
$emoji = new CustomEmoji;
$emoji->shortcode = $request->input('shortcode');
$emoji->domain = config('pixelfed.domain.app');
$emoji->save();
$fileName = $emoji->id . '.' . $request->emoji->extension();
$request->emoji->storeAs('public/emoji', $fileName);
$emoji->media_path = 'emoji/' . $fileName;
$emoji->save();
Cache::forget('pf:custom_emoji');
return redirect(route('admin.custom-emoji'));
}
public function customEmojiDelete(Request $request, $id)
{
abort_unless(config('federation.custom_emoji.enabled'), 404);
$emoji = CustomEmoji::findOrFail($id);
Storage::delete("public/{$emoji->media_path}");
Cache::forget('pf:custom_emoji');
$emoji->delete();
return redirect(route('admin.custom-emoji'));
}
public function customEmojiShowDuplicates(Request $request, $id)
{
abort_unless(config('federation.custom_emoji.enabled'), 404);
$emoji = CustomEmoji::orderBy('id')->whereDisabled(false)->whereShortcode($id)->firstOrFail();
$emojis = CustomEmoji::whereShortcode($id)->where('id', '!=', $emoji->id)->cursorPaginate(10);
return view('admin.custom-emoji.duplicates', compact('emoji', 'emojis'));
}
} }

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,8 @@ use App\{
Media, Media,
Notification, Notification,
Profile, Profile,
Status Status,
StatusArchived
}; };
use App\Transformer\Api\{ use App\Transformer\Api\{
AccountTransformer, AccountTransformer,
@ -36,9 +37,11 @@ use App\Jobs\VideoPipeline\{
VideoPostProcess, VideoPostProcess,
VideoThumbnail VideoThumbnail
}; };
use App\Services\AccountService;
use App\Services\NotificationService; use App\Services\NotificationService;
use App\Services\MediaPathService; use App\Services\MediaPathService;
use App\Services\MediaBlocklistService; use App\Services\MediaBlocklistService;
use App\Services\StatusService;
class BaseApiController extends Controller class BaseApiController extends Controller
{ {
@ -54,26 +57,40 @@ class BaseApiController extends Controller
public function notifications(Request $request) public function notifications(Request $request)
{ {
abort_if(!$request->user(), 403); abort_if(!$request->user(), 403);
$pid = $request->user()->profile_id; $pid = $request->user()->profile_id;
$pg = $request->input('pg'); $limit = $request->input('limit', 20);
if($pg == true) {
$timeago = Carbon::now()->subMonths(6); $since = $request->input('since_id');
$notifications = Notification::whereProfileId($pid) $min = $request->input('min_id');
->whereDate('created_at', '>', $timeago) $max = $request->input('max_id');
->latest()
->simplePaginate(10); if(!$since && !$min && !$max) {
$resource = new Fractal\Resource\Collection($notifications, new NotificationTransformer()); $min = 1;
$res = $this->fractal->createData($resource)->toArray(); }
$maxId = null;
$minId = null;
if($max) {
$res = NotificationService::getMax($pid, $max, $limit);
$ids = NotificationService::getRankedMaxId($pid, $max, $limit);
if(!empty($ids)) {
$maxId = max($ids);
$minId = min($ids);
}
} else { } else {
$this->validate($request, [ $res = NotificationService::getMin($pid, $min ?? $since, $limit);
'page' => 'nullable|integer|min:1|max:10', $ids = NotificationService::getRankedMinId($pid, $min ?? $since, $limit);
'limit' => 'nullable|integer|min:1|max:40' if(!empty($ids)) {
]); $maxId = max($ids);
$limit = $request->input('limit') ?? 10; $minId = min($ids);
$page = $request->input('page') ?? 1; }
$end = (int) $page * $limit; }
$start = (int) $end - $limit;
$res = NotificationService::get($pid, $start, $end); if(empty($res) && !Cache::has('pf:services:notifications:hasSynced:'.$pid)) {
Cache::put('pf:services:notifications:hasSynced:'.$pid, 1, 1209600);
NotificationService::warmCache($pid, 400, true);
} }
return response()->json($res); return response()->json($res);
@ -183,7 +200,6 @@ class BaseApiController extends Controller
$avatar = Avatar::whereProfileId($profile->id)->firstOrFail(); $avatar = Avatar::whereProfileId($profile->id)->firstOrFail();
$opath = $avatar->media_path; $opath = $avatar->media_path;
$avatar->media_path = "$public/$name"; $avatar->media_path = "$public/$name";
$avatar->thumb_path = null;
$avatar->change_count = ++$avatar->change_count; $avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = null; $avatar->last_processed_at = null;
$avatar->save(); $avatar->save();
@ -201,117 +217,17 @@ class BaseApiController extends Controller
public function showTempMedia(Request $request, $profileId, $mediaId, $timestamp) public function showTempMedia(Request $request, $profileId, $mediaId, $timestamp)
{ {
abort_if(!$request->user(), 403); abort(400, 'Endpoint deprecated');
abort_if(!$request->hasValidSignature(), 404);
abort_if(Auth::user()->profile_id != $profileId, 404);
$media = Media::whereProfileId(Auth::user()->profile_id)->findOrFail($mediaId);
$path = storage_path('app/'.$media->media_path);
return response()->file($path);
} }
public function uploadMedia(Request $request) public function uploadMedia(Request $request)
{ {
abort_if(!$request->user(), 403); abort(400, 'Endpoint deprecated');
$this->validate($request, [
'file.*' => function() {
return [
'required',
'mimes:' . config('pixelfed.media_types'),
'max:' . config('pixelfed.max_photo_size'),
];
},
'filter_name' => 'nullable|string|max:24',
'filter_class' => 'nullable|alpha_dash|max:24'
]);
$user = Auth::user();
$profile = $user->profile;
if(config('pixelfed.enforce_account_limit') == true) {
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
return Media::whereUserId($user->id)->sum('size') / 1000;
});
$limit = (int) config('pixelfed.max_account_size');
if ($size >= $limit) {
abort(403, 'Account size limit reached.');
}
}
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
$photo = $request->file('file');
$mimes = explode(',', config('pixelfed.media_types'));
if(in_array($photo->getMimeType(), $mimes) == false) {
return;
}
$storagePath = MediaPathService::get($user, 2);
$path = $photo->store($storagePath);
$hash = \hash_file('sha256', $photo);
abort_if(MediaBlocklistService::exists($hash) == true, 451);
$media = new Media();
$media->status_id = null;
$media->profile_id = $profile->id;
$media->user_id = $user->id;
$media->media_path = $path;
$media->original_sha256 = $hash;
$media->size = $photo->getSize();
$media->mime = $photo->getMimeType();
$media->filter_class = $filterClass;
$media->filter_name = $filterName;
$media->save();
$url = URL::temporarySignedRoute(
'temp-media', now()->addHours(1), ['profileId' => $profile->id, 'mediaId' => $media->id, 'timestamp' => time()]
);
switch ($media->mime) {
case 'image/jpeg':
case 'image/png':
ImageOptimize::dispatch($media);
break;
case 'video/mp4':
VideoThumbnail::dispatch($media);
$preview_url = '/storage/no-preview.png';
$url = '/storage/no-preview.png';
break;
default:
break;
}
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
$res = $this->fractal->createData($resource)->toArray();
$res['preview_url'] = $url;
$res['url'] = $url;
return response()->json($res);
} }
public function deleteMedia(Request $request) public function deleteMedia(Request $request)
{ {
abort_if(!$request->user(), 403); abort(400, 'Endpoint deprecated');
$this->validate($request, [
'id' => 'required|integer|min:1|exists:media,id'
]);
$media = Media::whereNull('status_id')
->whereUserId(Auth::id())
->findOrFail($request->input('id'));
Storage::delete($media->media_path);
Storage::delete($media->thumbnail_path);
$media->forceDelete();
return response()->json([
'msg' => 'Successfully deleted',
'code' => 200
]);
} }
public function verifyCredentials(Request $request) public function verifyCredentials(Request $request)
@ -320,17 +236,9 @@ class BaseApiController extends Controller
abort_if(!$user, 403); abort_if(!$user, 403);
if($user->status != null) { if($user->status != null) {
Auth::logout(); Auth::logout();
return redirect('/login'); abort(403);
} }
$key = 'user:last_active_at:id:'.$user->id; $res = AccountService::get($user->profile_id);
$ttl = now()->addMinutes(5);
Cache::remember($key, $ttl, function() use($user) {
$user->last_active_at = now();
$user->save();
return;
});
$resource = new Fractal\Resource\Item($user->profile, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray();
return response()->json($res); return response()->json($res);
} }
@ -351,26 +259,98 @@ class BaseApiController extends Controller
public function accountLikes(Request $request) public function accountLikes(Request $request)
{ {
$user = $request->user();
abort_if(!$request->user(), 403); abort_if(!$request->user(), 403);
$this->validate($request, [
'page' => 'sometimes|int|min:1|max:20',
'limit' => 'sometimes|int|min:1|max:10'
]);
$limit = 10; $user = $request->user();
$page = (int) $request->input('page', 1); $limit = $request->input('limit', 10);
if($page > 20) { $res = \DB::table('likes')
return []; ->whereProfileId($user->profile_id)
}
$favourites = $user->profile->likes()
->latest() ->latest()
->simplePaginate($limit) ->simplePaginate($limit)
->pluck('status_id'); ->map(function($id) {
$status = StatusService::get($id->status_id, false);
$status['favourited'] = true;
return $status;
})
->filter(function($post) {
return $post && isset($post['account']);
})
->values();
return response()->json($res);
}
$statuses = Status::find($favourites)->reverse(); public function archive(Request $request, $id)
{
abort_if(!$request->user(), 403);
$status = Status::whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->whereProfileId($request->user()->profile_id)
->findOrFail($id);
if($status->scope === 'archived') {
return [200];
}
$archive = new StatusArchived;
$archive->status_id = $status->id;
$archive->profile_id = $status->profile_id;
$archive->original_scope = $status->scope;
$archive->save();
$status->scope = 'archived';
$status->visibility = 'draft';
$status->save();
StatusService::del($status->id, true);
AccountService::syncPostCount($status->profile_id);
return [200];
}
public function unarchive(Request $request, $id)
{
abort_if(!$request->user(), 403);
$status = Status::whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->whereProfileId($request->user()->profile_id)
->findOrFail($id);
if($status->scope !== 'archived') {
return [200];
}
$archive = StatusArchived::whereStatusId($status->id)
->whereProfileId($status->profile_id)
->firstOrFail();
$status->scope = $archive->original_scope;
$status->visibility = $archive->original_scope;
$status->save();
$archive->delete();
StatusService::del($status->id, true);
AccountService::syncPostCount($status->profile_id);
return [200];
}
public function archivedPosts(Request $request)
{
abort_if(!$request->user(), 403);
$statuses = Status::whereProfileId($request->user()->profile_id)
->whereScope('archived')
->orderByDesc('id')
->simplePaginate(10);
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Collection($statuses, new StatusStatelessTransformer()); $resource = new Fractal\Resource\Collection($statuses, new StatusStatelessTransformer());
$res = $this->fractal->createData($resource)->toArray(); return $fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
} }
} }

View file

@ -34,7 +34,7 @@ class InstanceApiController extends Controller {
$res = [ $res = [
'uri' => config('pixelfed.domain.app'), 'uri' => config('pixelfed.domain.app'),
'title' => config('app.name'), 'title' => config_cache('app.name'),
'description' => '', 'description' => '',
'version' => config('pixelfed.version'), 'version' => config('pixelfed.version'),
'urls' => [], 'urls' => [],

View file

@ -27,7 +27,10 @@ class LoginController extends Controller
* *
* @var string * @var string
*/ */
protected $redirectTo = '/'; protected $redirectTo = '/i/web';
protected $maxAttempts = 5;
protected $decayMinutes = 60;
/** /**
* Create a new controller instance. * Create a new controller instance.

View file

@ -32,7 +32,7 @@ class RegisterController extends Controller
* *
* @var string * @var string
*/ */
protected $redirectTo = '/'; protected $redirectTo = '/i/web';
/** /**
* Create a new controller instance. * Create a new controller instance.
@ -44,6 +44,13 @@ class RegisterController extends Controller
$this->middleware('guest'); $this->middleware('guest');
} }
public function getRegisterToken()
{
return \Cache::remember('pf:register:rt', 900, function() {
return str_random(40);
});
}
/** /**
* Get a validator for an incoming registration request. * Get a validator for an incoming registration request.
* *
@ -76,7 +83,7 @@ class RegisterController extends Controller
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
} }
if (!ctype_alpha($value[0])) { if (!ctype_alnum($value[0])) {
return $fail('Username is invalid. Must start with a letter or number.'); return $fail('Username is invalid. Must start with a letter or number.');
} }
@ -110,8 +117,18 @@ class RegisterController extends Controller
}, },
]; ];
$rt = [
'required',
function ($attribute, $value, $fail) {
if($value !== $this->getRegisterToken()) {
return $fail('Something went wrong');
}
}
];
$rules = [ $rules = [
'agecheck' => 'required|accepted', 'agecheck' => 'required|accepted',
'rt' => $rt,
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'), 'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
'username' => $usernameRules, 'username' => $usernameRules,
'email' => $emailRules, 'email' => $emailRules,
@ -154,7 +171,7 @@ class RegisterController extends Controller
*/ */
public function showRegistrationForm() public function showRegistrationForm()
{ {
if(config('pixelfed.open_registration')) { if(config_cache('pixelfed.open_registration')) {
$limit = config('pixelfed.max_users'); $limit = config('pixelfed.max_users');
if($limit) { if($limit) {
abort_if($limit <= User::count(), 404); abort_if($limit <= User::count(), 404);
@ -175,12 +192,12 @@ class RegisterController extends Controller
*/ */
public function register(Request $request) public function register(Request $request)
{ {
abort_if(config('pixelfed.open_registration') == false, 400); abort_if(config_cache('pixelfed.open_registration') == false, 400);
$count = User::count(); $count = User::count();
$limit = config('pixelfed.max_users'); $limit = config('pixelfed.max_users');
if(false == config('pixelfed.open_registration') || $limit && $limit <= $count) { if(false == config_cache('pixelfed.open_registration') || $limit && $limit <= $count) {
return abort(403); return abort(403);
} }

View file

@ -35,7 +35,6 @@ class AvatarController extends Controller
$avatar = Avatar::firstOrNew(['profile_id' => $profile->id]); $avatar = Avatar::firstOrNew(['profile_id' => $profile->id]);
$currentAvatar = $avatar->recentlyCreated ? null : storage_path('app/'.$profile->avatar->media_path); $currentAvatar = $avatar->recentlyCreated ? null : storage_path('app/'.$profile->avatar->media_path);
$avatar->media_path = "$public/$name"; $avatar->media_path = "$public/$name";
$avatar->thumb_path = null;
$avatar->change_count = ++$avatar->change_count; $avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = null; $avatar->last_processed_at = null;
$avatar->save(); $avatar->save();
@ -121,10 +120,7 @@ class AvatarController extends Controller
$avatar = $profile->avatar; $avatar = $profile->avatar;
if( $avatar->media_path == 'public/avatars/default.png' || if( $avatar->media_path == 'public/avatars/default.png' ||
$avatar->thumb_path == 'public/avatars/default.png' || $avatar->media_path == 'public/avatars/default.jpg'
$avatar->media_path == 'public/avatars/default.jpg' ||
$avatar->thumb_path == 'public/avatars/default.jpg'
) { ) {
return; return;
} }
@ -133,12 +129,7 @@ class AvatarController extends Controller
@unlink(storage_path('app/' . $avatar->media_path)); @unlink(storage_path('app/' . $avatar->media_path));
} }
if(is_file(storage_path('app/' . $avatar->thumb_path))) {
@unlink(storage_path('app/' . $avatar->thumb_path));
}
$avatar->media_path = 'public/avatars/default.jpg'; $avatar->media_path = 'public/avatars/default.jpg';
$avatar->thumb_path = 'public/avatars/default.jpg';
$avatar->change_count = $avatar->change_count + 1; $avatar->change_count = $avatar->change_count + 1;
$avatar->save(); $avatar->save();

View file

@ -6,6 +6,7 @@ use App\Bookmark;
use App\Status; use App\Status;
use Auth; use Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\BookmarkService;
class BookmarkController extends Controller class BookmarkController extends Controller
{ {
@ -28,7 +29,10 @@ class BookmarkController extends Controller
); );
if (!$bookmark->wasRecentlyCreated) { if (!$bookmark->wasRecentlyCreated) {
BookmarkService::del($profile->id, $status->id);
$bookmark->delete(); $bookmark->delete();
} else {
BookmarkService::add($profile->id, $status->id);
} }
if ($request->ajax()) { if ($request->ajax()) {

View file

@ -18,6 +18,7 @@ use League\Fractal;
use App\Transformer\Api\StatusTransformer; use App\Transformer\Api\StatusTransformer;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Services\StatusService;
class CommentController extends Controller class CommentController extends Controller
{ {
@ -72,13 +73,11 @@ class CommentController extends Controller
$reply->visibility = $scope; $reply->visibility = $scope;
$reply->save(); $reply->save();
$status->reply_count++;
$status->save();
return $reply; return $reply;
}); });
NewStatusPipeline::dispatch($reply, false); StatusService::del($status->id);
NewStatusPipeline::dispatch($reply);
CommentPipeline::dispatch($status, $reply); CommentPipeline::dispatch($status, $reply);
if ($request->ajax()) { if ($request->ajax()) {

View file

@ -0,0 +1,735 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth, Cache, Storage, URL;
use Carbon\Carbon;
use App\{
Avatar,
Hashtag,
Like,
Media,
MediaTag,
Notification,
Profile,
Place,
Status,
UserFilter,
UserSetting
};
use App\Models\Poll;
use App\Transformer\Api\{
MediaTransformer,
MediaDraftTransformer,
StatusTransformer,
StatusStatelessTransformer
};
use League\Fractal;
use App\Util\Media\Filter;
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Jobs\AvatarPipeline\AvatarOptimize;
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use App\Jobs\ImageOptimizePipeline\ImageThumbnail;
use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Jobs\VideoPipeline\{
VideoOptimize,
VideoPostProcess,
VideoThumbnail
};
use App\Services\AccountService;
use App\Services\NotificationService;
use App\Services\MediaPathService;
use App\Services\MediaBlocklistService;
use App\Services\MediaStorageService;
use App\Services\MediaTagService;
use App\Services\StatusService;
use Illuminate\Support\Str;
use App\Util\Lexer\Autolink;
use App\Util\Lexer\Extractor;
use App\Util\Media\License;
class ComposeController extends Controller
{
protected $fractal;
public function __construct()
{
$this->middleware('auth');
$this->fractal = new Fractal\Manager();
$this->fractal->setSerializer(new ArraySerializer());
}
public function show(Request $request)
{
return view('status.compose');
}
public function mediaUpload(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'file.*' => function() {
return [
'required',
'mimes:' . config_cache('pixelfed.media_types'),
'max:' . config_cache('pixelfed.max_photo_size'),
];
},
'filter_name' => 'nullable|string|max:24',
'filter_class' => 'nullable|alpha_dash|max:24'
]);
$user = Auth::user();
$profile = $user->profile;
$limitKey = 'compose:rate-limit:media-upload:' . $user->id;
$limitTtl = now()->addMinutes(15);
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
return $dailyLimit >= 250;
});
abort_if($limitReached == true, 429);
if(config_cache('pixelfed.enforce_account_limit') == true) {
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
return Media::whereUserId($user->id)->sum('size') / 1000;
});
$limit = (int) config_cache('pixelfed.max_account_size');
if ($size >= $limit) {
abort(403, 'Account size limit reached.');
}
}
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
$photo = $request->file('file');
$mimes = explode(',', config_cache('pixelfed.media_types'));
abort_if(in_array($photo->getMimeType(), $mimes) == false, 400, 'Invalid media format');
$storagePath = MediaPathService::get($user, 2);
$path = $photo->store($storagePath);
$hash = \hash_file('sha256', $photo);
$mime = $photo->getMimeType();
abort_if(MediaBlocklistService::exists($hash) == true, 451);
$media = new Media();
$media->status_id = null;
$media->profile_id = $profile->id;
$media->user_id = $user->id;
$media->media_path = $path;
$media->original_sha256 = $hash;
$media->size = $photo->getSize();
$media->mime = $mime;
$media->filter_class = $filterClass;
$media->filter_name = $filterName;
$media->version = 3;
$media->save();
$preview_url = $media->url() . '?v=' . time();
$url = $media->url() . '?v=' . time();
switch ($media->mime) {
case 'image/jpeg':
case 'image/png':
case 'image/webp':
ImageOptimize::dispatch($media);
break;
case 'video/mp4':
VideoThumbnail::dispatch($media);
$preview_url = '/storage/no-preview.png';
$url = '/storage/no-preview.png';
break;
default:
break;
}
Cache::forget($limitKey);
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
$res = $this->fractal->createData($resource)->toArray();
$res['preview_url'] = $preview_url;
$res['url'] = $url;
return response()->json($res);
}
public function mediaUpdate(Request $request)
{
$this->validate($request, [
'id' => 'required',
'file' => function() {
return [
'required',
'mimes:' . config_cache('pixelfed.media_types'),
'max:' . config_cache('pixelfed.max_photo_size'),
];
},
]);
$user = Auth::user();
$limitKey = 'compose:rate-limit:media-updates:' . $user->id;
$limitTtl = now()->addMinutes(15);
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
return $dailyLimit >= 500;
});
abort_if($limitReached == true, 429);
$photo = $request->file('file');
$id = $request->input('id');
$media = Media::whereUserId($user->id)
->whereProfileId($user->profile_id)
->whereNull('status_id')
->findOrFail($id);
$media->save();
$fragments = explode('/', $media->media_path);
$name = last($fragments);
array_pop($fragments);
$dir = implode('/', $fragments);
$path = $photo->storeAs($dir, $name);
$res = [
'url' => $media->url() . '?v=' . time()
];
ImageOptimize::dispatch($media);
Cache::forget($limitKey);
return $res;
}
public function mediaDelete(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'id' => 'required|integer|min:1|exists:media,id'
]);
$media = Media::whereNull('status_id')
->whereUserId(Auth::id())
->findOrFail($request->input('id'));
MediaStorageService::delete($media, true);
$media->forceDelete();
return response()->json([
'msg' => 'Successfully deleted',
'code' => 200
]);
}
public function searchTag(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'q' => 'required|string|min:1|max:50'
]);
$q = $request->input('q');
if(Str::of($q)->startsWith('@')) {
if(strlen($q) < 3) {
return [];
}
$q = mb_substr($q, 1);
}
$blocked = UserFilter::whereFilterableType('App\Profile')
->whereFilterType('block')
->whereFilterableId($request->user()->profile_id)
->pluck('user_id');
$blocked->push($request->user()->profile_id);
$results = Profile::select('id','domain','username')
->whereNotIn('id', $blocked)
->whereNull('domain')
->where('username','like','%'.$q.'%')
->limit(15)
->get()
->map(function($r) {
return [
'id' => (string) $r->id,
'name' => $r->username,
'privacy' => true,
'avatar' => $r->avatarUrl()
];
});
return $results;
}
public function searchUntag(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'status_id' => 'required',
'profile_id' => 'required'
]);
$user = $request->user();
$status_id = $request->input('status_id');
$profile_id = (int) $request->input('profile_id');
abort_if((int) $user->profile_id !== $profile_id, 400);
$tag = MediaTag::whereStatusId($status_id)
->whereProfileId($profile_id)
->first();
if(!$tag) {
return [];
}
Notification::whereItemType('App\MediaTag')
->whereItemId($tag->id)
->whereProfileId($profile_id)
->whereAction('tagged')
->delete();
MediaTagService::untag($status_id, $profile_id);
return [200];
}
public function searchLocation(Request $request)
{
abort_if(!Auth::check(), 403);
$this->validate($request, [
'q' => 'required|string|max:100'
]);
$q = filter_var($request->input('q'), FILTER_SANITIZE_STRING);
$hash = hash('sha256', $q);
$key = 'search:location:id:' . $hash;
$places = Cache::remember($key, now()->addMinutes(15), function() use($q) {
$q = '%' . $q . '%';
return Place::where('name', 'like', $q)
->take(80)
->get()
->map(function($r) {
return [
'id' => $r->id,
'name' => $r->name,
'country' => $r->country,
'url' => $r->url()
];
});
});
return $places;
}
public function searchMentionAutocomplete(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'q' => 'required|string|min:2|max:50'
]);
$q = $request->input('q');
if(Str::of($q)->startsWith('@')) {
if(strlen($q) < 3) {
return [];
}
}
$blocked = UserFilter::whereFilterableType('App\Profile')
->whereFilterType('block')
->whereFilterableId($request->user()->profile_id)
->pluck('user_id');
$blocked->push($request->user()->profile_id);
$results = Profile::select('id','domain','username')
->whereNotIn('id', $blocked)
->where('username','like','%'.$q.'%')
->groupBy('domain')
->limit(15)
->get()
->map(function($profile) {
$username = $profile->domain ? substr($profile->username, 1) : $profile->username;
return [
'key' => '@' . str_limit($username, 30),
'value' => $username,
];
});
return $results;
}
public function searchHashtagAutocomplete(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'q' => 'required|string|min:2|max:50'
]);
$q = $request->input('q');
$results = Hashtag::select('slug')
->where('slug', 'like', '%'.$q.'%')
->whereIsNsfw(false)
->whereIsBanned(false)
->limit(5)
->get()
->map(function($tag) {
return [
'key' => '#' . $tag->slug,
'value' => $tag->slug
];
});
return $results;
}
public function store(Request $request)
{
$this->validate($request, [
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
'media.*' => 'required',
'media.*.id' => 'required|integer|min:1',
'media.*.filter_class' => 'nullable|alpha_dash|max:30',
'media.*.license' => 'nullable|string|max:140',
'media.*.alt' => 'nullable|string|max:'.config_cache('pixelfed.max_altext_length'),
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
'place' => 'nullable',
'comments_disabled' => 'nullable',
'tagged' => 'nullable',
'license' => 'nullable|integer|min:1|max:16'
// 'optimize_media' => 'nullable'
]);
if(config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
if($blockedKeywords !== null && $request->caption) {
$keywords = config('costar.keyword.block');
foreach($keywords as $kw) {
if(Str::contains($request->caption, $kw) == true) {
abort(400, 'Invalid object');
}
}
}
}
$user = Auth::user();
$profile = $user->profile;
$limitKey = 'compose:rate-limit:store:' . $user->id;
$limitTtl = now()->addMinutes(15);
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
$dailyLimit = Status::whereProfileId($user->profile_id)
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->where('created_at', '>', now()->subDays(1))
->count();
return $dailyLimit >= 100;
});
abort_if($limitReached == true, 429);
$license = in_array($request->input('license'), License::keys()) ? $request->input('license') : null;
$visibility = $request->input('visibility');
$medias = $request->input('media');
$attachments = [];
$status = new Status;
$mimes = [];
$place = $request->input('place');
$cw = $request->input('cw');
$tagged = $request->input('tagged');
$optimize_media = (bool) $request->input('optimize_media');
foreach($medias as $k => $media) {
if($k + 1 > config_cache('pixelfed.max_album_length')) {
continue;
}
$m = Media::findOrFail($media['id']);
if($m->profile_id !== $profile->id || $m->status_id) {
abort(403, 'Invalid media id');
}
$m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null;
$m->license = $license;
$m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null;
$m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k;
// if($optimize_media == false) {
// $m->skip_optimize = true;
// ImageThumbnail::dispatch($m);
// } else {
// ImageOptimize::dispatch($m);
// }
if($cw == true || $profile->cw == true) {
$m->is_nsfw = $cw;
$status->is_nsfw = $cw;
}
$m->save();
$attachments[] = $m;
array_push($mimes, $m->mime);
}
abort_if(empty($attachments), 422);
$mediaType = StatusController::mimeTypeCheck($mimes);
if(in_array($mediaType, ['photo', 'video', 'photo:album']) == false) {
abort(400, __('exception.compose.invalid.album'));
}
if($place && is_array($place)) {
$status->place_id = $place['id'];
}
if($request->filled('comments_disabled')) {
$status->comments_disabled = (bool) $request->input('comments_disabled');
}
$status->caption = strip_tags($request->caption);
$status->rendered = Autolink::create()->autolink($status->caption);
$status->scope = 'draft';
$status->profile_id = $profile->id;
$status->save();
foreach($attachments as $media) {
$media->status_id = $status->id;
$media->save();
}
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
$visibility = $profile->is_private ? 'private' : $visibility;
$cw = $profile->cw == true ? true : $cw;
$status->is_nsfw = $cw;
$status->visibility = $visibility;
$status->scope = $visibility;
$status->type = $mediaType;
$status->save();
foreach($tagged as $tg) {
$mt = new MediaTag;
$mt->status_id = $status->id;
$mt->media_id = $status->media->first()->id;
$mt->profile_id = $tg['id'];
$mt->tagged_username = $tg['name'];
$mt->is_public = true;
$mt->metadata = json_encode([
'_v' => 1,
]);
$mt->save();
MediaTagService::set($mt->status_id, $mt->profile_id);
MediaTagService::sendNotification($mt);
}
NewStatusPipeline::dispatch($status);
Cache::forget('user:account:id:'.$profile->user_id);
Cache::forget('_api:statuses:recent_9:'.$profile->id);
Cache::forget('profile:status_count:'.$profile->id);
Cache::forget('status:transformer:media:attachments:'.$status->id);
Cache::forget($user->storageUsedKey());
Cache::forget('profile:embed:' . $status->profile_id);
Cache::forget($limitKey);
return $status->url();
}
public function storeText(Request $request)
{
abort_unless(config('exp.top'), 404);
$this->validate($request, [
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
'place' => 'nullable',
'comments_disabled' => 'nullable',
'tagged' => 'nullable',
]);
if(config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
if($blockedKeywords !== null && $request->caption) {
$keywords = config('costar.keyword.block');
foreach($keywords as $kw) {
if(Str::contains($request->caption, $kw) == true) {
abort(400, 'Invalid object');
}
}
}
}
$user = Auth::user();
$profile = $user->profile;
$visibility = $request->input('visibility');
$status = new Status;
$place = $request->input('place');
$cw = $request->input('cw');
$tagged = $request->input('tagged');
if($place && is_array($place)) {
$status->place_id = $place['id'];
}
if($request->filled('comments_disabled')) {
$status->comments_disabled = (bool) $request->input('comments_disabled');
}
$status->caption = strip_tags($request->caption);
$status->profile_id = $profile->id;
$entities = Extractor::create()->extract($status->caption);
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
$cw = $profile->cw == true ? true : $cw;
$status->is_nsfw = $cw;
$status->visibility = $visibility;
$status->scope = $visibility;
$status->type = 'text';
$status->rendered = Autolink::create()->autolink($status->caption);
$status->entities = json_encode(array_merge([
'timg' => [
'version' => 0,
'bg_id' => 1,
'font_size' => strlen($status->caption) <= 140 ? 'h1' : 'h3',
'length' => strlen($status->caption),
]
], $entities), JSON_UNESCAPED_SLASHES);
$status->save();
foreach($tagged as $tg) {
$mt = new MediaTag;
$mt->status_id = $status->id;
$mt->media_id = $status->media->first()->id;
$mt->profile_id = $tg['id'];
$mt->tagged_username = $tg['name'];
$mt->is_public = true;
$mt->metadata = json_encode([
'_v' => 1,
]);
$mt->save();
MediaTagService::set($mt->status_id, $mt->profile_id);
MediaTagService::sendNotification($mt);
}
Cache::forget('user:account:id:'.$profile->user_id);
Cache::forget('_api:statuses:recent_9:'.$profile->id);
Cache::forget('profile:status_count:'.$profile->id);
return $status->url();
}
public function mediaProcessingCheck(Request $request)
{
$this->validate($request, [
'id' => 'required|integer|min:1'
]);
$media = Media::whereUserId($request->user()->id)
->whereNull('status_id')
->findOrFail($request->input('id'));
if(config('pixelfed.media_fast_process')) {
return [
'finished' => true
];
}
$finished = false;
switch ($media->mime) {
case 'image/jpeg':
case 'image/png':
case 'video/mp4':
$finished = config_cache('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at;
break;
default:
# code...
break;
}
return [
'finished' => $finished
];
}
public function composeSettings(Request $request)
{
$uid = $request->user()->id;
$default = [
'default_license' => 1,
'media_descriptions' => false,
'max_altext_length' => config_cache('pixelfed.max_altext_length')
];
$settings = AccountService::settings($uid);
if(isset($settings['other']) && isset($settings['other']['scope'])) {
$s = $settings['compose_settings'];
$s['default_scope'] = $settings['other']['scope'];
$settings['compose_settings'] = $s;
}
return array_merge($default, $settings['compose_settings']);
}
public function createPoll(Request $request)
{
$this->validate($request, [
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private',
'comments_disabled' => 'nullable',
'expiry' => 'required|in:60,360,1440,10080',
'pollOptions' => 'required|array|min:1|max:4'
]);
abort_if(config('instance.polls.enabled') == false, 404, 'Polls not enabled');
abort_if(Status::whereType('poll')
->whereProfileId($request->user()->profile_id)
->whereCaption($request->input('caption'))
->where('created_at', '>', now()->subDays(2))
->exists()
, 422, 'Duplicate detected.');
$status = new Status;
$status->profile_id = $request->user()->profile_id;
$status->caption = $request->input('caption');
$status->rendered = Autolink::create()->autolink($status->caption);
$status->visibility = 'draft';
$status->scope = 'draft';
$status->type = 'poll';
$status->local = true;
$status->save();
$poll = new Poll;
$poll->status_id = $status->id;
$poll->profile_id = $status->profile_id;
$poll->poll_options = $request->input('pollOptions');
$poll->expires_at = now()->addMinutes($request->input('expiry'));
$poll->cached_tallies = collect($poll->poll_options)->map(function($o) {
return 0;
})->toArray();
$poll->save();
$status->visibility = $request->input('visibility');
$status->scope = $request->input('visibility');
$status->save();
NewStatusPipeline::dispatch($status);
return ['url' => $status->url()];
}
}

View file

@ -390,7 +390,6 @@ class DirectMessageController extends Controller
$min_id = $request->input('min_id'); $min_id = $request->input('min_id');
$r = Profile::findOrFail($pid); $r = Profile::findOrFail($pid);
// $r = Profile::whereNull('domain')->findOrFail($pid);
if($min_id) { if($min_id) {
$res = DirectMessage::select('*') $res = DirectMessage::select('*')
@ -500,8 +499,8 @@ class DirectMessageController extends Controller
'file' => function() { 'file' => function() {
return [ return [
'required', 'required',
'mimes:' . config('pixelfed.media_types'), 'mimes:' . config_cache('pixelfed.media_types'),
'max:' . config('pixelfed.max_photo_size'), 'max:' . config_cache('pixelfed.max_photo_size'),
]; ];
}, },
'to_id' => 'required' 'to_id' => 'required'
@ -522,18 +521,18 @@ class DirectMessageController extends Controller
$hidden = false; $hidden = false;
} }
if(config('pixelfed.enforce_account_limit') == true) { if(config_cache('pixelfed.enforce_account_limit') == true) {
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) { $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
return Media::whereUserId($user->id)->sum('size') / 1000; return Media::whereUserId($user->id)->sum('size') / 1000;
}); });
$limit = (int) config('pixelfed.max_account_size'); $limit = (int) config_cache('pixelfed.max_account_size');
if ($size >= $limit) { if ($size >= $limit) {
abort(403, 'Account size limit reached.'); abort(403, 'Account size limit reached.');
} }
} }
$photo = $request->file('file'); $photo = $request->file('file');
$mimes = explode(',', config('pixelfed.media_types')); $mimes = explode(',', config_cache('pixelfed.media_types'));
if(in_array($photo->getMimeType(), $mimes) == false) { if(in_array($photo->getMimeType(), $mimes) == false) {
abort(403, 'Invalid or unsupported mime type.'); abort(403, 'Invalid or unsupported mime type.');
} }
@ -590,11 +589,15 @@ class DirectMessageController extends Controller
{ {
$this->validate($request, [ $this->validate($request, [
'q' => 'required|string|min:2|max:50', 'q' => 'required|string|min:2|max:50',
'remote' => 'nullable|boolean', 'remote' => 'nullable',
]); ]);
$q = $request->input('q'); $q = $request->input('q');
$r = $request->input('remote'); $r = $request->input('remote', false);
if($r && !Str::of($q)->contains('.')) {
return [];
}
if($r && Helpers::validateUrl($q)) { if($r && Helpers::validateUrl($q)) {
Helpers::profileFetch($q); Helpers::profileFetch($q);

View file

@ -24,6 +24,7 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Services\StatusHashtagService; use App\Services\StatusHashtagService;
use App\Services\SnowflakeService; use App\Services\SnowflakeService;
use App\Services\StatusService; use App\Services\StatusService;
use App\Services\UserFilterService;
class DiscoverController extends Controller class DiscoverController extends Controller
{ {
@ -37,7 +38,7 @@ class DiscoverController extends Controller
public function home(Request $request) public function home(Request $request)
{ {
abort_if(!Auth::check(), 403); abort_if(!Auth::check() && config('instance.discover.public') == false, 403);
return view('discover.home'); return view('discover.home');
} }
@ -54,63 +55,21 @@ class DiscoverController extends Controller
public function showCategory(Request $request, $slug) public function showCategory(Request $request, $slug)
{ {
abort_if(!Auth::check(), 403); abort(404);
$tag = DiscoverCategory::whereActive(true)
->whereSlug($slug)
->firstOrFail();
$posts = Cache::remember('discover:category-'.$tag->id.':posts', now()->addMinutes(15), function() use ($tag) {
$tagids = $tag->hashtags->pluck('id')->toArray();
$sids = StatusHashtag::whereIn('hashtag_id', $tagids)->orderByDesc('status_id')->take(500)->pluck('status_id')->toArray();
$posts = Status::whereScope('public')->whereIn('id', $sids)->whereNull('uri')->whereType('photo')->whereNull('in_reply_to_id')->whereNull('reblog_of_id')->orderByDesc('created_at')->take(39)->get();
return $posts;
});
$tag->posts_count = Cache::remember('discover:category-'.$tag->id.':posts_count', now()->addMinutes(30), function() use ($tag) {
return $tag->posts()->whereScope('public')->count();
});
return view('discover.tags.category', compact('tag', 'posts'));
} }
public function showLoops(Request $request) public function showLoops(Request $request)
{ {
if(config('exp.loops') != true) { abort(404);
return redirect('/');
}
return view('discover.loops.home');
} }
public function loopsApi(Request $request) public function loopsApi(Request $request)
{ {
abort_if(!config('exp.loops'), 403); abort(404);
// todo proper pagination, maybe LoopService
$res = Cache::remember('discover:loops:recent', now()->addHours(6), function() {
$loops = Status::whereType('video')
->whereNull('uri')
->whereScope('public')
->latest()
->take(18)
->get();
$resource = new Fractal\Resource\Collection($loops, new StatusStatelessTransformer());
return $this->fractal->createData($resource)->toArray();
});
return $res;
} }
public function loopWatch(Request $request) public function loopWatch(Request $request)
{ {
abort_if(!Auth::check(), 403);
abort_if(!config('exp.loops'), 403);
$this->validate($request, [
'id' => 'integer|min:1'
]);
$id = $request->input('id');
// todo log loops
return response()->json(200); return response()->json(200);
} }
@ -129,61 +88,51 @@ class DiscoverController extends Controller
$tag = $request->input('hashtag'); $tag = $request->input('hashtag');
$hashtag = Hashtag::whereName($tag)->firstOrFail(); $hashtag = Hashtag::whereName($tag)->firstOrFail();
$res['tags'] = StatusHashtagService::get($hashtag->id, $page, $end);
if($page == 1) { if($page == 1) {
$res['follows'] = HashtagFollow::whereUserId(Auth::id())->whereHashtagId($hashtag->id)->exists(); $res['follows'] = HashtagFollow::whereUserId(Auth::id())
->whereHashtagId($hashtag->id)
->exists();
} }
$res['hashtag'] = [
'name' => $hashtag->name,
'url' => $hashtag->url()
];
$res['tags'] = StatusHashtagService::get($hashtag->id, $page, $end);
return $res; return $res;
} }
public function profilesDirectory(Request $request) public function profilesDirectory(Request $request)
{ {
return redirect('/')->with('statusRedirect', 'The Profile Directory is unavailable at this time.'); return redirect('/')
return view('discover.profiles.home'); ->with('statusRedirect', 'The Profile Directory is unavailable at this time.');
} }
public function profilesDirectoryApi(Request $request) public function profilesDirectoryApi(Request $request)
{ {
$this->validate($request, [
'page' => 'integer|max:10'
]);
return ['error' => 'Temporarily unavailable.']; return ['error' => 'Temporarily unavailable.'];
$page = $request->input('page') ?? 1;
$key = 'discover:profiles:page:' . $page;
$ttl = now()->addHours(12);
$res = Cache::remember($key, $ttl, function() {
$profiles = Profile::whereNull('domain')
->whereNull('status')
->whereIsPrivate(false)
->has('statuses')
->whereIsSuggestable(true)
// ->inRandomOrder()
->simplePaginate(8);
$resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
return $this->fractal->createData($resource)->toArray();
});
return $res;
} }
public function trendingApi(Request $request) public function trendingApi(Request $request)
{ {
abort_if(config('instance.discover.public') == false && !Auth::check(), 403);
$this->validate($request, [ $this->validate($request, [
'range' => 'nullable|string|in:daily,monthly' 'range' => 'nullable|string|in:daily,monthly,yearly',
]); ]);
$range = $request->input('range') == 'monthly' ? 31 : 1; $range = $request->input('range');
$days = $range == 'monthly' ? 31 : ($range == 'daily' ? 1 : 365);
$ttls = [
1 => 1500,
31 => 14400,
365 => 86400
];
$key = ':api:discover:trending:v2.12:range:' . $days;
$key = ':api:discover:trending:v2.8:range:' . $range; $ids = Cache::remember($key, $ttls[$days], function() use($days) {
$ttl = now()->addMinutes(15);
$ids = Cache::remember($key, $ttl, function() use($range) {
$days = $range == 1 ? 2 : 31;
$min_id = SnowflakeService::byDate(now()->subDays($days)); $min_id = SnowflakeService::byDate(now()->subDays($days));
return Status::select( return DB::table('statuses')
->select(
'id', 'id',
'scope', 'scope',
'type', 'type',
@ -201,13 +150,20 @@ class DiscoverController extends Controller
]) ])
->whereIsNsfw(false) ->whereIsNsfw(false)
->orderBy('likes_count','desc') ->orderBy('likes_count','desc')
->take(15) ->take(30)
->pluck('id'); ->pluck('id');
}); });
$filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
$res = $ids->map(function($s) { $res = $ids->map(function($s) {
return StatusService::get($s); return StatusService::get($s);
}); })->filter(function($s) use($filtered) {
return
$s &&
!in_array($s['account']['id'], $filtered) &&
isset($s['account']);
})->values();
return response()->json($res); return response()->json($res);
} }
@ -217,7 +173,7 @@ class DiscoverController extends Controller
$res = StatusHashtag::select('hashtag_id', \DB::raw('count(*) as total')) $res = StatusHashtag::select('hashtag_id', \DB::raw('count(*) as total'))
->groupBy('hashtag_id') ->groupBy('hashtag_id')
->orderBy('total','desc') ->orderBy('total','desc')
->where('created_at', '>', now()->subDays(4)) ->where('created_at', '>', now()->subDays(90))
->take(9) ->take(9)
->get() ->get()
->map(function($h) { ->map(function($h) {
@ -234,22 +190,6 @@ class DiscoverController extends Controller
public function trendingPlaces(Request $request) public function trendingPlaces(Request $request)
{ {
$res = Status::select('place_id',DB::raw('count(place_id) as total')) return [];
->whereNotNull('place_id')
->where('created_at','>',now()->subDays(14))
->groupBy('place_id')
->orderBy('total')
->limit(4)
->get()
->map(function($s){
$p = $s->place;
return [
'name' => $p->name,
'country' => $p->country,
'url' => $p->url()
];
});
return $res;
} }
} }

View file

@ -3,6 +3,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Jobs\InboxPipeline\{ use App\Jobs\InboxPipeline\{
DeleteWorker,
InboxWorker, InboxWorker,
InboxValidator InboxValidator
}; };
@ -34,14 +35,14 @@ class FederationController extends Controller
public function nodeinfoWellKnown() public function nodeinfoWellKnown()
{ {
abort_if(!config('federation.nodeinfo.enabled'), 404); abort_if(!config('federation.nodeinfo.enabled'), 404);
return response()->json(Nodeinfo::wellKnown()) return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES)
->header('Access-Control-Allow-Origin','*'); ->header('Access-Control-Allow-Origin','*');
} }
public function nodeinfo() public function nodeinfo()
{ {
abort_if(!config('federation.nodeinfo.enabled'), 404); abort_if(!config('federation.nodeinfo.enabled'), 404);
return response()->json(Nodeinfo::get()) return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES)
->header('Access-Control-Allow-Origin','*'); ->header('Access-Control-Allow-Origin','*');
} }
@ -49,21 +50,27 @@ class FederationController extends Controller
{ {
abort_if(!config('federation.webfinger.enabled'), 400); abort_if(!config('federation.webfinger.enabled'), 400);
abort_if(!$request->filled('resource'), 400); abort_if(!$request->has('resource') || !$request->filled('resource'), 400);
$resource = $request->input('resource'); $resource = $request->input('resource');
$hash = hash('sha256', $resource);
$key = 'federation:webfinger:sha256:' . $hash;
if($cached = Cache::get($key)) {
return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES);
}
$domain = config('pixelfed.domain.app');
abort_if(strpos($resource, $domain) == false, 400);
$parsed = Nickname::normalizeProfileUrl($resource); $parsed = Nickname::normalizeProfileUrl($resource);
if($parsed['domain'] !== config('pixelfed.domain.app')) { if(empty($parsed) || $parsed['domain'] !== $domain) {
abort(400); abort(400);
} }
$username = $parsed['username']; $username = $parsed['username'];
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
if($profile->status != null) { abort_if($profile->status != null, 400);
return ProfileController::accountCheck($profile);
}
$webfinger = (new Webfinger($profile))->generate(); $webfinger = (new Webfinger($profile))->generate();
Cache::put($key, $webfinger, 1209600);
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT) return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES)
->header('Access-Control-Allow-Origin','*'); ->header('Access-Control-Allow-Origin','*');
} }
@ -79,7 +86,7 @@ class FederationController extends Controller
public function userOutbox(Request $request, $username) public function userOutbox(Request $request, $username)
{ {
abort_if(!config('federation.activitypub.enabled'), 404); abort_if(!config_cache('federation.activitypub.enabled'), 404);
abort_if(!config('federation.activitypub.outbox'), 404); abort_if(!config('federation.activitypub.outbox'), 404);
$profile = Profile::whereNull('domain') $profile = Profile::whereNull('domain')
@ -99,29 +106,41 @@ class FederationController extends Controller
public function userInbox(Request $request, $username) public function userInbox(Request $request, $username)
{ {
abort_if(!config('federation.activitypub.enabled'), 404); abort_if(!config_cache('federation.activitypub.enabled'), 404);
abort_if(!config('federation.activitypub.inbox'), 404); abort_if(!config('federation.activitypub.inbox'), 404);
$headers = $request->headers->all(); $headers = $request->headers->all();
$payload = $request->getContent(); $payload = $request->getContent();
$obj = json_decode($payload, true, 8);
if(isset($obj['type']) && $obj['type'] === 'Delete') {
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
} else {
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high'); dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high');
}
return; return;
} }
public function sharedInbox(Request $request) public function sharedInbox(Request $request)
{ {
abort_if(!config('federation.activitypub.enabled'), 404); abort_if(!config_cache('federation.activitypub.enabled'), 404);
abort_if(!config('federation.activitypub.sharedInbox'), 404); abort_if(!config('federation.activitypub.sharedInbox'), 404);
$headers = $request->headers->all(); $headers = $request->headers->all();
$payload = $request->getContent(); $payload = $request->getContent();
$obj = json_decode($payload, true, 8);
if(isset($obj['type']) && $obj['type'] === 'Delete') {
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
} else {
dispatch(new InboxWorker($headers, $payload))->onQueue('high'); dispatch(new InboxWorker($headers, $payload))->onQueue('high');
}
return; return;
} }
public function userFollowing(Request $request, $username) public function userFollowing(Request $request, $username)
{ {
abort_if(!config('federation.activitypub.enabled'), 404); abort_if(!config_cache('federation.activitypub.enabled'), 404);
$profile = Profile::whereNull('remote_url') $profile = Profile::whereNull('remote_url')
->whereUsername($username) ->whereUsername($username)
@ -144,7 +163,7 @@ class FederationController extends Controller
public function userFollowers(Request $request, $username) public function userFollowers(Request $request, $username)
{ {
abort_if(!config('federation.activitypub.enabled'), 404); abort_if(!config_cache('federation.activitypub.enabled'), 404);
$profile = Profile::whereNull('remote_url') $profile = Profile::whereNull('remote_url')
->whereUsername($username) ->whereUsername($username)

View file

@ -12,6 +12,7 @@ use Auth, Cache;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Jobs\FollowPipeline\FollowPipeline; use App\Jobs\FollowPipeline\FollowPipeline;
use App\Util\ActivityPub\Helpers; use App\Util\ActivityPub\Helpers;
use App\Services\FollowerService;
class FollowerController extends Controller class FollowerController extends Controller
{ {
@ -71,6 +72,8 @@ class FollowerController extends Controller
if($remote == true && config('federation.activitypub.remoteFollow') == true) { if($remote == true && config('federation.activitypub.remoteFollow') == true) {
$this->sendFollow($user, $target); $this->sendFollow($user, $target);
} }
FollowerService::add($user->id, $target->id);
} elseif ($private == false && $isFollowing == 0) { } elseif ($private == false && $isFollowing == 0) {
if($user->following()->count() >= Follower::MAX_FOLLOWING) { if($user->following()->count() >= Follower::MAX_FOLLOWING) {
abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts'); abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts');
@ -87,6 +90,7 @@ class FollowerController extends Controller
if($remote == true && config('federation.activitypub.remoteFollow') == true) { if($remote == true && config('federation.activitypub.remoteFollow') == true) {
$this->sendFollow($user, $target); $this->sendFollow($user, $target);
} }
FollowerService::add($user->id, $target->id);
FollowPipeline::dispatch($follower); FollowPipeline::dispatch($follower);
} else { } else {
if($force == true) { if($force == true) {
@ -101,6 +105,7 @@ class FollowerController extends Controller
Follower::whereProfileId($user->id) Follower::whereProfileId($user->id)
->whereFollowingId($target->id) ->whereFollowingId($target->id)
->delete(); ->delete();
FollowerService::remove($user->id, $target->id);
} }
} }

View file

@ -17,6 +17,9 @@ trait Instagram
{ {
public function instagram() public function instagram()
{ {
if(config_cache('pixelfed.import.instagram.enabled') != true) {
abort(404, 'Feature not enabled');
}
return view('settings.import.instagram.home'); return view('settings.import.instagram.home');
} }

View file

@ -8,6 +8,9 @@ trait Mastodon
{ {
public function mastodon() public function mastodon()
{ {
if(config_cache('pixelfed.import.instagram.enabled') != true) {
abort(404, 'Feature not enabled');
}
return view('settings.import.mastodon.home'); return view('settings.import.mastodon.home');
} }
} }

View file

@ -11,10 +11,6 @@ class ImportController extends Controller
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
if(config('pixelfed.import.instagram.enabled') != true) {
abort(404, 'Feature not enabled');
}
} }
} }

View file

@ -12,7 +12,7 @@ class InstanceActorController extends Controller
{ {
$res = Cache::rememberForever(InstanceActor::PROFILE_KEY, function() { $res = Cache::rememberForever(InstanceActor::PROFILE_KEY, function() {
$res = (new InstanceActor())->first()->getActor(); $res = (new InstanceActor())->first()->getActor();
return json_encode($res); return json_encode($res, JSON_UNESCAPED_SLASHES);
}); });
return response($res)->header('Content-Type', 'application/json'); return response($res)->header('Content-Type', 'application/json');
} }

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\{ use App\{
AccountInterstitial, AccountInterstitial,
Bookmark,
DirectMessage, DirectMessage,
DiscoverCategory, DiscoverCategory,
Hashtag, Hashtag,
@ -16,9 +17,11 @@ use App\{
Profile, Profile,
StatusHashtag, StatusHashtag,
Status, Status,
User,
UserFilter, UserFilter,
}; };
use Auth,Cache; use Auth,Cache;
use Illuminate\Support\Facades\Redis;
use Carbon\Carbon; use Carbon\Carbon;
use League\Fractal; use League\Fractal;
use App\Transformer\Api\{ use App\Transformer\Api\{
@ -28,6 +31,7 @@ use App\Transformer\Api\{
}; };
use App\Util\Media\Filter; use App\Util\Media\Filter;
use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Jobs\ModPipeline\HandleSpammerPipeline;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -35,6 +39,11 @@ use Illuminate\Support\Str;
use App\Services\MediaTagService; use App\Services\MediaTagService;
use App\Services\ModLogService; use App\Services\ModLogService;
use App\Services\PublicTimelineService; use App\Services\PublicTimelineService;
use App\Services\SnowflakeService;
use App\Services\StatusService;
use App\Services\UserFilterService;
use App\Services\DiscoverService;
use App\Services\BookmarkService;
class InternalApiController extends Controller class InternalApiController extends Controller
{ {
@ -61,61 +70,21 @@ class InternalApiController extends Controller
public function discoverPosts(Request $request) public function discoverPosts(Request $request)
{ {
$profile = Auth::user()->profile; $pid = $request->user()->profile_id;
$pid = $profile->id; $filters = UserFilterService::filters($pid);
$following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(15), function() use ($pid) { $forYou = DiscoverService::getForYou();
return Follower::whereProfileId($pid)->pluck('following_id')->toArray(); $posts = $forYou->take(50)->map(function($post) {
}); return StatusService::get($post);
$filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(15), function() use($pid) {
$private = Profile::whereIsPrivate(true)
->orWhere('unlisted', true)
->orWhere('status', '!=', null)
->pluck('id')
->toArray();
$filters = UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id')
->toArray();
return array_merge($private, $filters);
});
$following = array_merge($following, $filters);
$sql = config('database.default') !== 'pgsql';
$posts = Status::select(
'id',
'caption',
'is_nsfw',
'profile_id',
'type',
'uri',
'created_at'
)
->whereNull('uri')
->whereIn('type', ['photo','photo:album', 'video'])
->whereIsNsfw(false)
->whereVisibility('public')
->whereNotIn('profile_id', $following)
->when($sql, function($q, $s) {
return $q->where('created_at', '>', now()->subMonths(3));
}) })
->with('media') ->filter(function($post) use($filters) {
->inRandomOrder() return $post &&
->latest() isset($post['account']) &&
->take(39) isset($post['account']['id']) &&
->get(); !in_array($post['account']['id'], $filters);
$res = [
'posts' => $posts->map(function($post) {
return [
'type' => $post->type,
'url' => $post->url(),
'thumb' => $post->thumb(),
];
}) })
]; ->take(12)
return response()->json($res); ->values();
return response()->json(compact('posts'));
} }
public function directMessage(Request $request, $profileId, $threadId) public function directMessage(Request $request, $profileId, $threadId)
@ -140,13 +109,15 @@ class InternalApiController extends Controller
public function statusReplies(Request $request, int $id) public function statusReplies(Request $request, int $id)
{ {
$this->validate($request, [
'limit' => 'nullable|int|min:1|max:6'
]);
$parent = Status::whereScope('public')->findOrFail($id); $parent = Status::whereScope('public')->findOrFail($id);
$limit = $request->input('limit') ?? 3;
$children = Status::whereInReplyToId($parent->id) $children = Status::whereInReplyToId($parent->id)
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->take(3) ->take($limit)
->get(); ->get();
$resource = new Fractal\Resource\Collection($children, new StatusTransformer()); $resource = new Fractal\Resource\Collection($children, new StatusTransformer());
$res = $this->fractal->createData($resource)->toArray(); $res = $this->fractal->createData($resource)->toArray();
@ -181,8 +152,8 @@ class InternalApiController extends Controller
Rule::in([ Rule::in([
'addcw', 'addcw',
'remcw', 'remcw',
'unlist' 'unlist',
'spammer'
]) ])
], ],
'item_id' => 'required|integer|min:1', 'item_id' => 'required|integer|min:1',
@ -197,9 +168,12 @@ class InternalApiController extends Controller
$item_id = $request->input('item_id'); $item_id = $request->input('item_id');
$item_type = $request->input('item_type'); $item_type = $request->input('item_type');
$status = Status::findOrFail($item_id);
$author = User::whereProfileId($status->profile_id)->first();
abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts');
switch($action) { switch($action) {
case 'addcw': case 'addcw':
$status = Status::findOrFail($item_id);
$status->is_nsfw = true; $status->is_nsfw = true;
$status->save(); $status->save();
ModLogService::boot() ModLogService::boot()
@ -215,7 +189,6 @@ class InternalApiController extends Controller
->accessLevel('admin') ->accessLevel('admin')
->save(); ->save();
if($status->uri == null) { if($status->uri == null) {
$media = $status->media; $media = $status->media;
$ai = new AccountInterstitial; $ai = new AccountInterstitial;
@ -246,7 +219,6 @@ class InternalApiController extends Controller
break; break;
case 'remcw': case 'remcw':
$status = Status::findOrFail($item_id);
$status->is_nsfw = false; $status->is_nsfw = false;
$status->save(); $status->save();
ModLogService::boot() ModLogService::boot()
@ -272,7 +244,6 @@ class InternalApiController extends Controller
break; break;
case 'unlist': case 'unlist':
$status = Status::whereScope('public')->findOrFail($item_id);
$status->scope = $status->visibility = 'unlisted'; $status->scope = $status->visibility = 'unlisted';
$status->save(); $status->save();
PublicTimelineService::del($status->id); PublicTimelineService::del($status->id);
@ -317,135 +288,55 @@ class InternalApiController extends Controller
$u->save(); $u->save();
} }
break; break;
case 'spammer':
HandleSpammerPipeline::dispatch($status->profile);
ModLogService::boot()
->user(Auth::user())
->objectUid($status->profile->user_id)
->objectId($status->id)
->objectType('App\User::class')
->action('admin.status.moderate')
->metadata([
'action' => 'spammer',
'message' => 'Success!'
])
->accessLevel('admin')
->save();
break;
} }
StatusService::del($status->id, true);
return ['msg' => 200]; return ['msg' => 200];
} }
public function composePost(Request $request) public function composePost(Request $request)
{ {
$this->validate($request, [ abort(400, 'Endpoint deprecated');
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
'media.*' => 'required',
'media.*.id' => 'required|integer|min:1',
'media.*.filter_class' => 'nullable|alpha_dash|max:30',
'media.*.license' => 'nullable|string|max:140',
'media.*.alt' => 'nullable|string|max:140',
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
'place' => 'nullable',
'comments_disabled' => 'nullable',
'tagged' => 'nullable'
]);
if(config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
if($blockedKeywords !== null && $request->caption) {
$keywords = config('costar.keyword.block');
foreach($keywords as $kw) {
if(Str::contains($request->caption, $kw) == true) {
abort(400, 'Invalid object');
}
}
}
}
$user = Auth::user();
$profile = $user->profile;
$visibility = $request->input('visibility');
$medias = $request->input('media');
$attachments = [];
$status = new Status;
$mimes = [];
$place = $request->input('place');
$cw = $request->input('cw');
$tagged = $request->input('tagged');
foreach($medias as $k => $media) {
if($k + 1 > config('pixelfed.max_album_length')) {
continue;
}
$m = Media::findOrFail($media['id']);
if($m->profile_id !== $profile->id || $m->status_id) {
abort(403, 'Invalid media id');
}
$m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null;
$m->license = $media['license'];
$m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null;
$m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k;
if($cw == true || $profile->cw == true) {
$m->is_nsfw = $cw;
$status->is_nsfw = $cw;
}
$m->save();
$attachments[] = $m;
array_push($mimes, $m->mime);
}
$mediaType = StatusController::mimeTypeCheck($mimes);
if(in_array($mediaType, ['photo', 'video', 'photo:album']) == false) {
abort(400, __('exception.compose.invalid.album'));
}
if($place && is_array($place)) {
$status->place_id = $place['id'];
}
if($request->filled('comments_disabled')) {
$status->comments_disabled = (bool) $request->input('comments_disabled');
}
$status->caption = strip_tags($request->caption);
$status->scope = 'draft';
$status->profile_id = $profile->id;
$status->save();
foreach($attachments as $media) {
$media->status_id = $status->id;
$media->save();
}
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
$cw = $profile->cw == true ? true : $cw;
$status->is_nsfw = $cw;
$status->visibility = $visibility;
$status->scope = $visibility;
$status->type = $mediaType;
$status->save();
foreach($tagged as $tg) {
$mt = new MediaTag;
$mt->status_id = $status->id;
$mt->media_id = $status->media->first()->id;
$mt->profile_id = $tg['id'];
$mt->tagged_username = $tg['name'];
$mt->is_public = true; // (bool) $tg['privacy'] ?? 1;
$mt->metadata = json_encode([
'_v' => 1,
]);
$mt->save();
MediaTagService::set($mt->status_id, $mt->profile_id);
MediaTagService::sendNotification($mt);
}
NewStatusPipeline::dispatch($status);
Cache::forget('user:account:id:'.$profile->user_id);
Cache::forget('_api:statuses:recent_9:'.$profile->id);
Cache::forget('profile:status_count:'.$profile->id);
Cache::forget($user->storageUsedKey());
return $status->url();
} }
public function bookmarks(Request $request) public function bookmarks(Request $request)
{ {
$statuses = Auth::user()->profile $pid = $request->user()->profile_id;
->bookmarks() $res = Bookmark::whereProfileId($pid)
->withCount(['likes','comments']) ->orderByDesc('created_at')
->orderBy('created_at', 'desc') ->simplePaginate(10)
->simplePaginate(10); ->map(function($bookmark) use($pid) {
$status = StatusService::get($bookmark->status_id, false);
if(!$status) {
return false;
}
$status['bookmarked_at'] = $bookmark->created_at->format('c');
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer()); if($status) {
$res = $this->fractal->createData($resource)->toArray(); BookmarkService::add($pid, $status['id']);
}
return $status;
})
->filter(function($bookmark) {
return $bookmark && isset($bookmark['id']);
})
->values();
return response()->json($res); return response()->json($res);
} }
@ -528,25 +419,25 @@ class InternalApiController extends Controller
public function remoteProfile(Request $request, $id) public function remoteProfile(Request $request, $id)
{ {
$profile = Profile::whereNull('status') return redirect('/i/web/profile/' . $id);
->whereNotNull('domain')
->findOrFail($id);
$user = Auth::user();
return view('profile.remote', compact('profile', 'user'));
} }
public function remoteStatus(Request $request, $profileId, $statusId) public function remoteStatus(Request $request, $profileId, $statusId)
{ {
$user = Profile::whereNull('status') return redirect('/i/web/post/' . $statusId);
->whereNotNull('domain') }
->findOrFail($profileId);
$status = Status::whereProfileId($user->id) public function requestEmailVerification(Request $request)
->whereNull('reblog_of_id') {
->whereIn('visibility', ['public', 'unlisted']) $pid = $request->user()->profile_id;
->findOrFail($statusId); $exists = Redis::sismember('email:manual', $pid);
$template = $status->in_reply_to_id ? 'status.reply' : 'status.remote'; return view('account.email.request_verification', compact('exists'));
return view($template, compact('user', 'status')); }
public function requestEmailVerificationStore(Request $request)
{
$pid = $request->user()->profile_id;
Redis::sadd('email:manual', $pid);
return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']);
} }
} }

View file

@ -3,12 +3,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Jobs\LikePipeline\LikePipeline; use App\Jobs\LikePipeline\LikePipeline;
use App\Jobs\LikePipeline\UnlikePipeline;
use App\Like; use App\Like;
use App\Status; use App\Status;
use App\User; use App\User;
use Auth; use Auth;
use Cache; use Cache;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\StatusService;
class LikeController extends Controller class LikeController extends Controller
{ {
@ -27,15 +29,11 @@ class LikeController extends Controller
$profile = $user->profile; $profile = $user->profile;
$status = Status::findOrFail($request->input('item')); $status = Status::findOrFail($request->input('item'));
$count = $status->likes()->count(); if (Like::whereStatusId($status->id)->whereProfileId($profile->id)->exists()) {
if ($status->likes()->whereProfileId($profile->id)->count() !== 0) {
$like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail(); $like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail();
$like->forceDelete(); UnlikePipeline::dispatch($like);
$count--;
$status->likes_count = $count;
$status->save();
} else { } else {
$count = $status->likes_count > 4 ? $status->likes_count : $status->likes()->count();
$like = Like::firstOrCreate([ $like = Like::firstOrCreate([
'profile_id' => $user->profile_id, 'profile_id' => $user->profile_id,
'status_id' => $status->id 'status_id' => $status->id
@ -58,9 +56,10 @@ class LikeController extends Controller
} }
Cache::forget('status:'.$status->id.':likedby:userid:'.$user->id); Cache::forget('status:'.$status->id.':likedby:userid:'.$user->id);
StatusService::refresh($status->id);
if ($request->ajax()) { if ($request->ajax()) {
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count]; $response = ['code' => 200, 'msg' => 'Like saved', 'count' => 0];
} else { } else {
$response = redirect($status->url()); $response = redirect($status->url());
} }

View file

@ -22,39 +22,6 @@ class MediaController extends Controller
public function composeUpdate(Request $request, $id) public function composeUpdate(Request $request, $id)
{ {
$this->validate($request, [ abort(400, 'Endpoint deprecated');
'file' => function() {
return [
'required',
'mimes:' . config('pixelfed.media_types'),
'max:' . config('pixelfed.max_photo_size'),
];
},
]);
$user = Auth::user();
$photo = $request->file('file');
$media = Media::whereUserId($user->id)
->whereProfileId($user->profile_id)
->whereNull('status_id')
->findOrFail($id);
$media->version = 2;
$media->save();
$fragments = explode('/', $media->media_path);
$name = last($fragments);
array_pop($fragments);
$dir = implode('/', $fragments);
$path = $photo->storeAs($dir, $name);
$res = [];
$res['url'] = URL::temporarySignedRoute(
'temp-media', now()->addHours(1), ['profileId' => $media->profile_id, 'mediaId' => $media->id, 'timestamp' => time()]
);
ImageOptimize::dispatch($media);
return $res;
} }
} }

View file

@ -20,44 +20,7 @@ class MediaTagController extends Controller
public function usernameLookup(Request $request) public function usernameLookup(Request $request)
{ {
abort_if(!$request->user(), 403); abort(404);
$this->validate($request, [
'q' => 'required|string|min:1|max:50'
]);
$q = $request->input('q');
if(Str::of($q)->startsWith('@')) {
if(strlen($q) < 3) {
return [];
}
$q = mb_substr($q, 1);
}
$blocked = UserFilter::whereFilterableType('App\Profile')
->whereFilterType('block')
->whereFilterableId($request->user()->profile_id)
->pluck('user_id');
$blocked->push($request->user()->profile_id);
$results = Profile::select('id','domain','username')
->whereNotIn('id', $blocked)
->whereNull('domain')
->where('username','like','%'.$q.'%')
->limit(15)
->get()
->map(function($r) {
return [
'id' => (string) $r->id,
'name' => $r->username,
'privacy' => true,
'avatar' => $r->avatarUrl()
];
});
return $results;
} }
public function untagProfile(Request $request) public function untagProfile(Request $request)

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Status;
use App\Models\Poll;
use App\Models\PollVote;
use App\Services\PollService;
use App\Services\FollowerService;
class PollController extends Controller
{
public function getPoll(Request $request, $id)
{
abort_if(!config_cache('instance.polls.enabled'), 404);
$poll = Poll::findOrFail($id);
$status = Status::findOrFail($poll->status_id);
if($status->scope != 'public') {
abort_if(!$request->user(), 403);
if($request->user()->profile_id != $status->profile_id) {
abort_if(!FollowerService::follows($request->user()->profile_id, $status->profile_id), 404);
}
}
$pid = $request->user() ? $request->user()->profile_id : false;
$poll = PollService::getById($id, $pid);
return $poll;
}
public function vote(Request $request, $id)
{
abort_if(!config_cache('instance.polls.enabled'), 404);
abort_unless($request->user(), 403);
$this->validate($request, [
'choices' => 'required|array'
]);
$pid = $request->user()->profile_id;
$poll_id = $id;
$choices = $request->input('choices');
// todo: implement multiple choice
$choice = $choices[0];
$poll = Poll::findOrFail($poll_id);
abort_if(now()->gt($poll->expires_at), 422, 'Poll expired.');
abort_if(PollVote::wherePollId($poll_id)->whereProfileId($pid)->exists(), 400, 'Already voted.');
$vote = new PollVote;
$vote->status_id = $poll->status_id;
$vote->profile_id = $pid;
$vote->poll_id = $poll->id;
$vote->choice = $choice;
$vote->save();
$poll->votes_count = $poll->votes_count + 1;
$poll->cached_tallies = collect($poll->getTallies())->map(function($tally, $key) use($choice) {
return $choice == $key ? $tally + 1 : $tally;
})->toArray();
$poll->save();
PollService::del($poll->status_id);
$res = PollService::get($poll->status_id, $pid);
return $res;
}
}

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Auth; use Auth;
use Cache; use Cache;
use DB;
use View; use View;
use App\Follower; use App\Follower;
use App\FollowRequest; use App\FollowRequest;
@ -13,6 +14,9 @@ use App\Story;
use App\User; use App\User;
use App\UserFilter; use App\UserFilter;
use League\Fractal; use League\Fractal;
use App\Services\AccountService;
use App\Services\FollowerService;
use App\Services\StatusService;
use App\Util\Lexer\Nickname; use App\Util\Lexer\Nickname;
use App\Util\Webfinger\Webfinger; use App\Util\Webfinger\Webfinger;
use App\Transformer\ActivityPub\ProfileOutbox; use App\Transformer\ActivityPub\ProfileOutbox;
@ -27,7 +31,7 @@ class ProfileController extends Controller
->whereUsername($username) ->whereUsername($username)
->firstOrFail(); ->firstOrFail();
if($request->wantsJson() && config('federation.activitypub.enabled')) { if($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
return $this->showActivityPub($request, $user); return $this->showActivityPub($request, $user);
} }
return $this->buildProfile($request, $user); return $this->buildProfile($request, $user);
@ -47,13 +51,13 @@ class ProfileController extends Controller
}); });
if ($user->is_private == true) { if ($user->is_private == true) {
abort(404); $profile = null;
return view('profile.private', compact('user'));
} }
$owner = false; $owner = false;
$is_following = false; $is_following = false;
$is_admin = $user->user->is_admin;
$profile = $user; $profile = $user;
$settings = [ $settings = [
'crawlable' => $settings->crawlable, 'crawlable' => $settings->crawlable,
@ -114,7 +118,7 @@ class ProfileController extends Controller
{ {
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
if ($request->wantsJson() && config('federation.activitypub.enabled')) { if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
return $this->showActivityPub($request, $user); return $this->showActivityPub($request, $user);
} }
@ -172,7 +176,7 @@ class ProfileController extends Controller
public function showActivityPub(Request $request, $user) public function showActivityPub(Request $request, $user)
{ {
abort_if(!config('federation.activitypub.enabled'), 404); abort_if(!config_cache('federation.activitypub.enabled'), 404);
abort_if($user->domain, 404); abort_if($user->domain, 404);
$fractal = new Fractal\Manager(); $fractal = new Fractal\Manager();
@ -185,19 +189,35 @@ class ProfileController extends Controller
{ {
abort_if(!config('federation.atom.enabled'), 404); abort_if(!config('federation.atom.enabled'), 404);
$profile = $user = Profile::whereNull('status')->whereNull('domain')->whereUsername($user)->whereIsPrivate(false)->firstOrFail(); $pid = AccountService::usernameToId($user);
if($profile->status != null) {
return $this->accountCheck($profile); abort_if(!$pid, 404);
}
if($profile->is_private || Auth::check()) { $profile = AccountService::get($pid);
$blocked = $this->blockedProfileCheck($profile);
$check = $this->privateProfileCheck($profile, null); abort_if(!$profile || $profile['locked'] || !$profile['local'], 404);
if($check || $blocked) {
return redirect($profile->url()); $items = DB::table('statuses')
} ->whereProfileId($pid)
} ->whereVisibility('public')
$items = $profile->statuses()->whereHas('media')->whereIn('visibility',['public', 'unlisted'])->orderBy('created_at', 'desc')->take(10)->get(); ->whereType('photo')
return response()->view('atom.user', compact('profile', 'items')) ->latest()
->take(10)
->get()
->map(function($status) {
return StatusService::get($status->id);
})
->filter(function($status) {
return $status &&
isset($status['account']) &&
isset($status['media_attachments']) &&
count($status['media_attachments']);
})
->values();
$permalink = config('app.url') . "/users/{$profile['username']}.atom";
return response()
->view('atom.user', compact('profile', 'items', 'permalink'))
->header('Content-Type', 'application/atom+xml'); ->header('Content-Type', 'application/atom+xml');
} }
@ -225,24 +245,26 @@ class ProfileController extends Controller
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
} }
$content = Cache::remember('profile:embed:'.$profile->id, now()->addHours(12), function() use($profile) { if(AccountService::canEmbed($profile->user_id) == false) {
return View::make('profile.embed')->with(compact('profile'))->render(); return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
}); }
return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); $profile = AccountService::get($profile->id);
$res = view('profile.embed', compact('profile'));
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
} }
public function stories(Request $request, $username) public function stories(Request $request, $username)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
$pid = $profile->id; $pid = $profile->id;
$authed = Auth::user()->profile; $authed = Auth::user()->profile_id;
abort_if($pid != $authed->id && $profile->followedBy($authed) == false, 404); abort_if($pid != $authed && !FollowerService::follows($authed, $pid), 404);
$exists = Story::whereProfileId($pid) $exists = Story::whereProfileId($pid)
->where('expires_at', '>', now()) ->whereActive(true)
->count(); ->exists();
abort_unless($exists > 0, 404); abort_unless($exists, 404);
return view('profile.story', compact('pid', 'profile')); return view('profile.story', compact('pid', 'profile'));
} }
} }

View file

@ -12,9 +12,11 @@ use App\{
Profile, Profile,
StatusHashtag, StatusHashtag,
Status, Status,
StatusView,
UserFilter UserFilter
}; };
use Auth,Cache; use Auth, Cache, DB;
use Illuminate\Support\Facades\Redis;
use Carbon\Carbon; use Carbon\Carbon;
use League\Fractal; use League\Fractal;
use App\Transformer\Api\{ use App\Transformer\Api\{
@ -25,14 +27,21 @@ use App\Transformer\Api\{
}; };
use App\Services\{ use App\Services\{
AccountService, AccountService,
BookmarkService,
FollowerService,
LikeService,
PublicTimelineService, PublicTimelineService,
ProfileService,
ReblogService,
RelationshipService,
StatusService,
SnowflakeService,
UserFilterService UserFilterService
}; };
use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Jobs\StatusPipeline\NewStatusPipeline;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
class PublicApiController extends Controller class PublicApiController extends Controller
{ {
protected $fractal; protected $fractal;
@ -82,29 +91,44 @@ class PublicApiController extends Controller
} }
} }
public function getStatus(Request $request, $id)
{
abort_if(!$request->user(), 403);
$status = StatusService::get($id, false);
abort_if(!$status, 404);
if(in_array($status['visibility'], ['public', 'unlisted'])) {
return $status;
}
$pid = $request->user()->profile_id;
if($status['account']['id'] == $pid) {
return $status;
}
if($status['visibility'] == 'private') {
if(FollowerService::follows($pid, $status['account']['id'])) {
return $status;
}
}
abort(404);
}
public function status(Request $request, $username, int $postid) public function status(Request $request, $username, int $postid)
{ {
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail(); $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
$status = Status::whereProfileId($profile->id)->findOrFail($postid); $status = Status::whereProfileId($profile->id)->findOrFail($postid);
$this->scopeCheck($profile, $status); $this->scopeCheck($profile, $status);
if(!Auth::check()) { if(!$request->user()) {
$res = Cache::remember('wapi:v1:status:stateless_byid:' . $status->id, now()->addMinutes(30), function() use($status) { $res = ['status' => StatusService::get($status->id)];
} else {
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer()); $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
$res = [ $res = [
'status' => $this->fractal->createData($item)->toArray(), 'status' => $this->fractal->createData($item)->toArray(),
]; ];
return $res;
});
return response()->json($res);
} }
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
$res = [
'status' => $this->fractal->createData($item)->toArray(),
];
return response()->json($res); return response()->json($res);
} }
public function statusState(Request $request, $username, int $postid) public function statusState(Request $request, $username, $postid)
{ {
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail(); $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
$status = Status::whereProfileId($profile->id)->findOrFail($postid); $status = Status::whereProfileId($profile->id)->findOrFail($postid);
@ -150,14 +174,9 @@ class PublicApiController extends Controller
if(Auth::check()) { if(Auth::check()) {
$p = Auth::user()->profile; $p = Auth::user()->profile;
$filtered = UserFilter::whereUserId($p->id) $scope = $p->id == $status->profile_id || FollowerService::follows($p->id, $profile->id) ? ['public', 'private', 'unlisted'] : ['public','unlisted'];
->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id')->toArray();
$scope = $p->id == $status->profile_id ? ['public', 'private'] : ['public'];
} else { } else {
$filtered = []; $scope = ['public', 'unlisted'];
$scope = ['public'];
} }
if($request->filled('min_id') || $request->filled('max_id')) { if($request->filled('min_id') || $request->filled('max_id')) {
@ -165,8 +184,7 @@ class PublicApiController extends Controller
$replies = $status->comments() $replies = $status->comments()
->whereNull('reblog_of_id') ->whereNull('reblog_of_id')
->whereIn('scope', $scope) ->whereIn('scope', $scope)
->whereNotIn('profile_id', $filtered) ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
->select('id', 'caption', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
->where('id', '>=', $request->min_id) ->where('id', '>=', $request->min_id)
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->paginate($limit); ->paginate($limit);
@ -175,18 +193,16 @@ class PublicApiController extends Controller
$replies = $status->comments() $replies = $status->comments()
->whereNull('reblog_of_id') ->whereNull('reblog_of_id')
->whereIn('scope', $scope) ->whereIn('scope', $scope)
->whereNotIn('profile_id', $filtered) ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
->select('id', 'caption', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
->where('id', '<=', $request->max_id) ->where('id', '<=', $request->max_id)
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->paginate($limit); ->paginate($limit);
} }
} else { } else {
$replies = $status->comments() $replies = Status::whereInReplyToId($status->id)
->whereNull('reblog_of_id') ->whereNull('reblog_of_id')
->whereIn('scope', $scope) ->whereIn('scope', $scope)
->whereNotIn('profile_id', $filtered) ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
->select('id', 'caption', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->paginate($limit); ->paginate($limit);
} }
@ -199,9 +215,15 @@ class PublicApiController extends Controller
public function statusLikes(Request $request, $username, $id) public function statusLikes(Request $request, $username, $id)
{ {
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail(); abort_if(!$request->user(), 404);
$status = Status::whereProfileId($profile->id)->findOrFail($id); $status = Status::findOrFail($id);
$this->scopeCheck($profile, $status); $this->scopeCheck($status->profile, $status);
$page = $request->input('page');
if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
return response()->json([
'data' => []
]);
}
$likes = $this->getLikes($status); $likes = $this->getLikes($status);
return response()->json([ return response()->json([
'data' => $likes 'data' => $likes
@ -210,9 +232,16 @@ class PublicApiController extends Controller
public function statusShares(Request $request, $username, $id) public function statusShares(Request $request, $username, $id)
{ {
abort_if(!$request->user(), 404);
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail(); $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
$status = Status::whereProfileId($profile->id)->findOrFail($id); $status = Status::whereProfileId($profile->id)->findOrFail($id);
$this->scopeCheck($profile, $status); $this->scopeCheck($profile, $status);
$page = $request->input('page');
if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
return response()->json([
'data' => []
]);
}
$shares = $this->getShares($status); $shares = $this->getShares($status);
return response()->json([ return response()->json([
'data' => $shares 'data' => $shares
@ -273,51 +302,42 @@ class PublicApiController extends Controller
$max = $request->input('max_id'); $max = $request->input('max_id');
$limit = $request->input('limit') ?? 3; $limit = $request->input('limit') ?? 3;
$user = $request->user(); $user = $request->user();
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
$key = 'user:last_active_at:id:'.$user->id; if(config('exp.cached_public_timeline') == false) {
$ttl = now()->addMinutes(5);
Cache::remember($key, $ttl, function() use($user) {
$user->last_active_at = now();
$user->save();
return;
});
$filtered = UserFilter::whereUserId($user->profile_id)
->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id')->toArray();
if($min || $max) { if($min || $max) {
$dir = $min ? '>' : '<'; $dir = $min ? '>' : '<';
$id = $min ?? $max; $id = $min ?? $max;
$timeline = Status::select( $timeline = Status::select(
'id', 'id',
'uri',
'caption',
'rendered',
'profile_id', 'profile_id',
'type', 'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope', 'scope',
'local', 'local'
'reply_count', )
'comments_disabled', ->where('id', $dir, $id)
'place_id', ->whereNull(['in_reply_to_id', 'reblog_of_id'])
'likes_count', ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
'reblogs_count',
'created_at',
'updated_at'
)->where('id', $dir, $id)
->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotIn('profile_id', $filtered)
->whereLocal(true) ->whereLocal(true)
->whereScope('public') ->whereScope('public')
->where('created_at', '>', now()->subMonths(3)) ->orderBy('id', 'desc')
->orderBy('created_at', 'desc')
->limit($limit) ->limit($limit)
->get(); ->get()
->map(function($s) use ($user) {
$status = StatusService::getFull($s->id, $user->profile_id);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
return $s && isset($s['account']) && in_array($s['account']['id'], $filtered) == false;
})
->values();
$res = $timeline->toArray();
} else { } else {
$timeline = Status::select( $timeline = Status::select(
'id', 'id',
@ -338,20 +358,66 @@ class PublicApiController extends Controller
'likes_count', 'likes_count',
'reblogs_count', 'reblogs_count',
'updated_at' 'updated_at'
)->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) )
->whereNotIn('profile_id', $filtered) ->whereNull(['in_reply_to_id', 'reblog_of_id'])
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->with('profile', 'hashtags', 'mentions') ->with('profile', 'hashtags', 'mentions')
->whereLocal(true) ->whereLocal(true)
->whereScope('public') ->whereScope('public')
->where('created_at', '>', now()->subMonths(3)) ->orderBy('id', 'desc')
->orderBy('created_at', 'desc') ->limit($limit)
->simplePaginate($limit); ->get()
->map(function($s) use ($user) {
$status = StatusService::getFull($s->id, $user->profile_id);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
return $s && in_array($s['account']['id'], $filtered) == false;
})
->values();
$res = $timeline->toArray();
}
} else {
Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() {
if(PublicTimelineService::count() == 0) {
PublicTimelineService::warmCache(true, 400);
}
});
if ($max) {
$feed = PublicTimelineService::getRankedMaxId($max, $limit);
} else if ($min) {
$feed = PublicTimelineService::getRankedMinId($min, $limit);
} else {
$feed = PublicTimelineService::get(0, $limit);
} }
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer()); $res = collect($feed)
$res = $this->fractal->createData($fractal)->toArray(); ->map(function($k) use($user) {
return response()->json($res); $status = StatusService::get($k);
if($user) {
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $k);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
}
return $status;
})
->filter(function($s) use($filtered) {
return isset($s['account']) && in_array($s['account']['id'], $filtered) == false;
})
->values()
->toArray();
}
return response()->json($res);
} }
public function homeTimelineApi(Request $request) public function homeTimelineApi(Request $request)
@ -364,9 +430,13 @@ class PublicApiController extends Controller
'page' => 'nullable|integer|max:40', 'page' => 'nullable|integer|max:40',
'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'limit' => 'nullable|integer|max:40' 'limit' => 'nullable|integer|max:40',
'recent_feed' => 'nullable',
'recent_min' => 'nullable|integer'
]); ]);
$recentFeed = $request->input('recent_feed') == 'true';
$recentFeedMin = $request->input('recent_min');
$page = $request->input('page'); $page = $request->input('page');
$min = $request->input('min_id'); $min = $request->input('min_id');
$max = $request->input('max_id'); $max = $request->input('max_id');
@ -374,38 +444,166 @@ class PublicApiController extends Controller
$user = $request->user(); $user = $request->user();
$key = 'user:last_active_at:id:'.$user->id; $key = 'user:last_active_at:id:'.$user->id;
$ttl = now()->addMinutes(5); $ttl = now()->addMinutes(20);
Cache::remember($key, $ttl, function() use($user) { Cache::remember($key, $ttl, function() use($user) {
$user->last_active_at = now(); $user->last_active_at = now();
$user->save(); $user->save();
return; return;
}); });
// TODO: Use redis for timelines $pid = $user->profile_id;
// $timeline = Timeline::build()->local();
$pid = Auth::user()->profile->id;
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
$following = Follower::whereProfileId($pid)->pluck('following_id'); $following = Follower::whereProfileId($pid)->pluck('following_id');
return $following->push($pid)->toArray(); return $following->push($pid)->toArray();
}); });
// $private = Cache::remember('profiles:private', now()->addMinutes(1440), function() { if($recentFeed == true) {
// return Profile::whereIsPrivate(true) $key = 'profile:home-timeline-cursor:'.$user->id;
// ->orWhere('unlisted', true) $ttl = now()->addMinutes(30);
// ->orWhere('status', '!=', null) $min = Cache::remember($key, $ttl, function() use($pid) {
// ->pluck('id'); $res = StatusView::whereProfileId($pid)->orderByDesc('status_id')->first();
// }); return $res ? $res->status_id : null;
});
}
// $private = $private->diff($following)->flatten(); $filtered = $user ? UserFilterService::filters($user->profile_id) : [];
$types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'];
// $types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album', 'text'];
// $filters = UserFilter::whereUserId($pid) $textOnlyReplies = false;
// ->whereFilterableType('App\Profile')
// ->whereIn('filter_type', ['mute', 'block'])
// ->pluck('filterable_id')->toArray();
// $filtered = array_merge($private->toArray(), $filters);
$filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : []; if(config('exp.top')) {
$textOnlyPosts = (bool) Redis::zscore('pf:tl:top', $pid);
$textOnlyReplies = (bool) Redis::zscore('pf:tl:replies', $pid);
if($textOnlyPosts) {
array_push($types, 'text');
}
}
if(config('exp.polls') == true) {
array_push($types, 'poll');
}
if($min || $max) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;
return Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at',
'updated_at'
)
->whereIn('type', $types)
->when($textOnlyReplies != true, function($q, $textOnlyReplies) {
return $q->whereNull('in_reply_to_id');
})
->with('profile', 'hashtags', 'mentions')
->where('id', $dir, $id)
->whereIn('profile_id', $following)
->whereIn('visibility',['public', 'unlisted', 'private'])
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
return $s && in_array($s['account']['id'], $filtered) == false;
})
->values()
->toArray();
} else {
return Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at',
'updated_at'
)
->whereIn('type', $types)
->when(!$textOnlyReplies, function($q, $textOnlyReplies) {
return $q->whereNull('in_reply_to_id');
})
->with('profile', 'hashtags', 'mentions')
->whereIn('profile_id', $following)
->whereIn('visibility',['public', 'unlisted', 'private'])
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
if(!$status) {
return false;
}
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
})
->filter(function($s) use($filtered) {
return $s && in_array($s['account']['id'], $filtered) == false;
})
->values()
->toArray();
}
}
public function networkTimelineApi(Request $request)
{
abort_if(!Auth::check(), 403);
abort_if(config('federation.network_timeline') == false, 404);
$this->validate($request,[
'page' => 'nullable|integer|max:40',
'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'limit' => 'nullable|integer|max:30'
]);
$page = $request->input('page');
$min = $request->input('min_id');
$max = $request->input('max_id');
$limit = $request->input('limit') ?? 3;
$user = $request->user();
$amin = SnowflakeService::byDate(now()->subDays(490));
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
if($min || $max) { if($min || $max) {
$dir = $min ? '>' : '<'; $dir = $min ? '>' : '<';
@ -413,69 +611,56 @@ class PublicApiController extends Controller
$timeline = Status::select( $timeline = Status::select(
'id', 'id',
'uri', 'uri',
'caption',
'rendered',
'profile_id',
'type', 'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope', 'scope',
'local',
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at', 'created_at',
'updated_at' )
)->whereIn('type', ['text','photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->with('profile', 'hashtags', 'mentions')
->where('id', $dir, $id) ->where('id', $dir, $id)
->whereIn('profile_id', $following) ->whereNull(['in_reply_to_id', 'reblog_of_id'])
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)
->whereIn('visibility',['public', 'unlisted', 'private']) ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->limit($limit) ->limit($limit)
->get(); ->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} else { } else {
$timeline = Status::select( $timeline = Status::select(
'id', 'id',
'uri', 'uri',
'caption',
'rendered',
'profile_id',
'type', 'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope', 'scope',
'local',
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at', 'created_at',
'updated_at' )
)->whereIn('type', ['text','photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) ->whereNull(['in_reply_to_id', 'reblog_of_id'])
->with('profile', 'hashtags', 'mentions')
->whereIn('profile_id', $following)
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)
->whereIn('visibility',['public', 'unlisted', 'private']) ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->simplePaginate($limit); ->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} }
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray();
return response()->json($res); return response()->json($res);
}
public function networkTimelineApi(Request $request)
{
return response()->json([]);
} }
public function relationships(Request $request) public function relationships(Request $request)
@ -484,17 +669,20 @@ class PublicApiController extends Controller
return response()->json([]); return response()->json([]);
} }
$pid = $request->user()->profile_id;
$this->validate($request, [ $this->validate($request, [
'id' => 'required|array|min:1|max:20', 'id' => 'required|array|min:1|max:20',
'id.*' => 'required|integer' 'id.*' => 'required|integer'
]); ]);
$ids = collect($request->input('id')); $ids = collect($request->input('id'));
$filtered = $ids->filter(function($v) { $res = $ids->filter(function($v) use($pid) {
return $v != Auth::user()->profile->id; return $v != $pid;
})
->map(function($id) use($pid) {
return RelationshipService::get($pid, $id);
}); });
$relations = Profile::whereNull('status')->findOrFail($filtered->all());
$fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer());
$res = $this->fractal->createData($fractal)->toArray();
return response()->json($res); return response()->json($res);
} }
@ -506,48 +694,80 @@ class PublicApiController extends Controller
public function accountFollowers(Request $request, $id) public function accountFollowers(Request $request, $id)
{ {
abort_unless(Auth::check(), 403); abort_if(!$request->user(), 403);
$profile = Profile::with('user')->whereNull('status')->whereNull('domain')->findOrFail($id); $account = AccountService::get($id);
if(Auth::id() != $profile->user_id && $profile->is_private || !$profile->user->settings->show_profile_followers) { abort_if(!$account, 404);
return response()->json([]); $pid = $request->user()->profile_id;
if($pid != $account['id']) {
if($account['locked']) {
if(FollowerService::follows($pid, $account['id'])) {
return [];
} }
$followers = $profile->followers()->orderByDesc('followers.created_at')->paginate(10); }
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray(); if(AccountService::hiddenFollowers($id)) {
return [];
}
if($request->has('page') && $request->page >= 5) {
return [];
}
}
$res = DB::table('followers')
->select('id', 'profile_id', 'following_id')
->whereFollowingId($account['id'])
->orderByDesc('id')
->simplePaginate(10)
->map(function($follower) {
return AccountService::get($follower->profile_id);
})
->filter(function($account) {
return $account && isset($account['id']);
})
->values()
->toArray();
return response()->json($res); return response()->json($res);
} }
public function accountFollowing(Request $request, $id) public function accountFollowing(Request $request, $id)
{ {
abort_unless(Auth::check(), 403); abort_if(!$request->user(), 403);
$account = AccountService::get($id);
abort_if(!$account, 404);
$pid = $request->user()->profile_id;
$profile = Profile::with('user') if($pid != $account['id']) {
->whereNull('status') if($account['locked']) {
->whereNull('domain') if(FollowerService::follows($pid, $account['id'])) {
->findOrFail($id); return [];
// filter by username
$search = $request->input('fbu');
$owner = Auth::id() == $profile->user_id;
$filter = ($owner == true) && ($search != null);
abort_if($owner == false && $profile->is_private == true && !$profile->followedBy(Auth::user()->profile), 404);
abort_if($profile->user->settings->show_profile_following == false && $owner == false, 404);
if($search) {
abort_if(!$owner, 404);
$following = $profile->following()
->where('profiles.username', 'like', '%'.$search.'%')
->orderByDesc('followers.created_at')
->paginate(10);
} else {
$following = $profile->following()
->orderByDesc('followers.created_at')
->paginate(10);
} }
$resource = new Fractal\Resource\Collection($following, new AccountTransformer()); }
$res = $this->fractal->createData($resource)->toArray();
if(AccountService::hiddenFollowing($id)) {
return [];
}
if($request->has('page') && $request->page >= 5) {
return [];
}
}
$res = DB::table('followers')
->select('id', 'profile_id', 'following_id')
->whereProfileId($account['id'])
->orderByDesc('id')
->simplePaginate(10)
->map(function($follower) {
return AccountService::get($follower->following_id);
})
->filter(function($account) {
return $account && isset($account['id']);
})
->values()
->toArray();
return response()->json($res); return response()->json($res);
} }
@ -564,116 +784,78 @@ class PublicApiController extends Controller
'limit' => 'nullable|integer|min:1|max:24' 'limit' => 'nullable|integer|min:1|max:24'
]); ]);
$profile = Profile::whereNull('status')->findOrFail($id); $user = $request->user();
$profile = AccountService::get($id);
abort_if(!$profile, 404);
$limit = $request->limit ?? 9; $limit = $request->limit ?? 9;
$max_id = $request->max_id; $max_id = $request->max_id;
$min_id = $request->min_id; $min_id = $request->min_id;
$scope = $request->only_media == true ? $scope = ['photo', 'photo:album', 'video', 'video:album'];
['photo', 'photo:album', 'video', 'video:album'] : $onlyMedia = $request->input('only_media', true);
['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
if($profile->is_private) { if(!$min_id && !$max_id) {
if(!Auth::check()) { $min_id = 1;
}
if($profile['locked']) {
if(!$user) {
return response()->json([]); return response()->json([]);
} }
$pid = Auth::user()->profile->id; $pid = $user->profile_id;
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
$following = Follower::whereProfileId($pid)->pluck('following_id'); $following = Follower::whereProfileId($pid)->pluck('following_id');
return $following->push($pid)->toArray(); return $following->push($pid)->toArray();
}); });
$visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : []; $visibility = true == in_array($profile['id'], $following) ? ['public', 'unlisted', 'private'] : [];
} else { } else {
if(Auth::check()) { if($user) {
$pid = Auth::user()->profile->id; $pid = $user->profile_id;
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
$following = Follower::whereProfileId($pid)->pluck('following_id'); $following = Follower::whereProfileId($pid)->pluck('following_id');
return $following->push($pid)->toArray(); return $following->push($pid)->toArray();
}); });
$visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; $visibility = true == in_array($profile['id'], $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
} else { } else {
$visibility = ['public', 'unlisted']; $visibility = ['public', 'unlisted'];
} }
} }
$tag = in_array('private', $visibility) ? 'private' : 'public';
if($min_id == 1 && $limit == 9 && $tag == 'public') {
$limit = 9;
$scope = ['photo', 'photo:album', 'video', 'video:album'];
$key = '_api:statuses:recent_9:'.$profile->id;
$res = Cache::remember($key, now()->addHours(24), function() use($profile, $scope, $visibility, $limit) {
$dir = '>';
$id = 1;
$timeline = Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'likes_count',
'reblogs_count',
'scope',
'visibility',
'local',
'place_id',
'comments_disabled',
'cw_summary',
'created_at',
'updated_at'
)->whereProfileId($profile->id)
->whereIn('type', $scope)
->where('id', $dir, $id)
->whereIn('visibility', $visibility)
->limit($limit)
->orderByDesc('id')
->get();
$resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer());
$res = $this->fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
});
return $res;
}
$dir = $min_id ? '>' : '<'; $dir = $min_id ? '>' : '<';
$id = $min_id ?? $max_id; $id = $min_id ?? $max_id;
$timeline = Status::select( $res = Status::whereProfileId($profile['id'])
'id', ->whereNull('in_reply_to_id')
'uri', ->whereNull('reblog_of_id')
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'likes_count',
'reblogs_count',
'scope',
'visibility',
'local',
'place_id',
'comments_disabled',
'cw_summary',
'created_at',
'updated_at'
)->whereProfileId($profile->id)
->whereIn('type', $scope) ->whereIn('type', $scope)
->where('id', $dir, $id) ->where('id', $dir, $id)
->whereIn('visibility', $visibility) ->whereIn('scope', $visibility)
->limit($limit) ->limit($limit)
->orderByDesc('id') ->orderByDesc('id')
->get(); ->get()
->map(function($s) use($user) {
$resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer()); try {
$res = $this->fractal->createData($resource)->toArray(); $status = StatusService::get($s->id, false);
} catch (\Exception $e) {
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); $status = false;
}
if($user && $status) {
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
}
return $status;
})
->filter(function($s) use($onlyMedia) {
if($onlyMedia) {
if(
!isset($s['media_attachments']) ||
!is_array($s['media_attachments']) ||
empty($s['media_attachments'])
) {
return false;
}
}
return $s;
})
->values();
return response()->json($res);
} }
} }

View file

@ -98,11 +98,27 @@ class ReportController extends Controller
$object_type = $request->input('type'); $object_type = $request->input('type');
$msg = $request->input('msg'); $msg = $request->input('msg');
$object = null; $object = null;
$types = ['spam', 'sensitive', 'abusive']; $types = [
// original 3
'spam',
'sensitive',
'abusive',
// new
'underage',
'copyright',
'impersonation',
'scam',
'terrorism'
];
if (!in_array($reportType, $types)) { if (!in_array($reportType, $types)) {
if($request->wantsJson()) {
return abort(400, 'Invalid report type');
} else {
return redirect('/timeline')->with('error', 'Invalid report type'); return redirect('/timeline')->with('error', 'Invalid report type');
} }
}
switch ($object_type) { switch ($object_type) {
case 'post': case 'post':
@ -115,17 +131,29 @@ class ReportController extends Controller
break; break;
default: default:
if($request->wantsJson()) {
return abort(400, 'Invalid report type');
} else {
return redirect('/timeline')->with('error', 'Invalid report type'); return redirect('/timeline')->with('error', 'Invalid report type');
}
break; break;
} }
if ($exists !== 0) { if ($exists !== 0) {
if($request->wantsJson()) {
return response()->json(200);
} else {
return redirect('/timeline')->with('error', 'You have already reported this!'); return redirect('/timeline')->with('error', 'You have already reported this!');
} }
}
if ($object->profile_id == $profile->id) { if ($object->profile_id == $profile->id) {
if($request->wantsJson()) {
return response()->json(200);
} else {
return redirect('/timeline')->with('error', 'You cannot report your own content!'); return redirect('/timeline')->with('error', 'You cannot report your own content!');
} }
}
$report = new Report(); $report = new Report();
$report->profile_id = $profile->id; $report->profile_id = $profile->id;
@ -134,9 +162,13 @@ class ReportController extends Controller
$report->object_type = $object_type; $report->object_type = $object_type;
$report->reported_profile_id = $object->profile_id; $report->reported_profile_id = $object->profile_id;
$report->type = $request->input('report'); $report->type = $request->input('report');
$report->message = $request->input('msg'); $report->message = e($request->input('msg'));
$report->save(); $report->save();
if($request->wantsJson()) {
return response()->json(200);
} else {
return redirect('/timeline')->with('status', 'Report successfully sent!'); return redirect('/timeline')->with('status', 'Report successfully sent!');
} }
} }
}

View file

@ -84,7 +84,7 @@ class SearchController extends Controller
$hash = hash('sha256', $tag); $hash = hash('sha256', $tag);
if( Helpers::validateUrl($tag) != false && if( Helpers::validateUrl($tag) != false &&
Helpers::validateLocalUrl($tag) != true && Helpers::validateLocalUrl($tag) != true &&
config('federation.activitypub.enabled') == true && config_cache('federation.activitypub.enabled') == true &&
config('federation.activitypub.remoteFollow') == true config('federation.activitypub.remoteFollow') == true
) { ) {
$remote = Helpers::fetchFromUrl($tag); $remote = Helpers::fetchFromUrl($tag);
@ -203,7 +203,7 @@ class SearchController extends Controller
$ttl = now()->addHours(2); $ttl = now()->addHours(2);
if( Helpers::validateUrl($tag) != false && if( Helpers::validateUrl($tag) != false &&
Helpers::validateLocalUrl($tag) != true && Helpers::validateLocalUrl($tag) != true &&
config('federation.activitypub.enabled') == true && config_cache('federation.activitypub.enabled') == true &&
config('federation.activitypub.remoteFollow') == true config('federation.activitypub.remoteFollow') == true
) { ) {
$remote = Helpers::fetchFromUrl($tag); $remote = Helpers::fetchFromUrl($tag);
@ -321,13 +321,18 @@ class SearchController extends Controller
if(Status::whereUri($tag)->whereLocal(false)->exists()) { if(Status::whereUri($tag)->whereLocal(false)->exists()) {
$item = Status::whereUri($tag)->first(); $item = Status::whereUri($tag)->first();
$media = $item->firstMedia();
$url = null;
if($media) {
$url = $media->remote_url;
}
$this->tokens['posts'] = [[ $this->tokens['posts'] = [[
'count' => 0, 'count' => 0,
'url' => "/i/web/post/_/$item->profile_id/$item->id", 'url' => "/i/web/post/_/$item->profile_id/$item->id",
'type' => 'status', 'type' => 'status',
'username' => $item->profile->username, 'username' => $item->profile->username,
'caption' => $item->rendered ?? $item->caption, 'caption' => $item->rendered ?? $item->caption,
'thumb' => $item->firstMedia()->remote_url, 'thumb' => $url,
'timestamp' => $item->created_at->diffForHumans() 'timestamp' => $item->created_at->diffForHumans()
]]; ]];
} }
@ -336,13 +341,18 @@ class SearchController extends Controller
if(isset($remote['type']) && $remote['type'] == 'Note') { if(isset($remote['type']) && $remote['type'] == 'Note') {
$item = Helpers::statusFetch($tag); $item = Helpers::statusFetch($tag);
$media = $item->firstMedia();
$url = null;
if($media) {
$url = $media->remote_url;
}
$this->tokens['posts'] = [[ $this->tokens['posts'] = [[
'count' => 0, 'count' => 0,
'url' => "/i/web/post/_/$item->profile_id/$item->id", 'url' => "/i/web/post/_/$item->profile_id/$item->id",
'type' => 'status', 'type' => 'status',
'username' => $item->profile->username, 'username' => $item->profile->username,
'caption' => $item->rendered ?? $item->caption, 'caption' => $item->rendered ?? $item->caption,
'thumb' => $item->firstMedia()->remote_url, 'thumb' => $url,
'timestamp' => $item->created_at->diffForHumans() 'timestamp' => $item->created_at->diffForHumans()
]]; ]];
} }

View file

@ -4,6 +4,12 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Auth; use Auth;
use App\AccountLog;
use App\Follower;
use App\Like;
use App\Status;
use App\StatusHashtag;
use Illuminate\Support\Facades\Cache;
class SeasonalController extends Controller class SeasonalController extends Controller
{ {
@ -14,7 +20,220 @@ class SeasonalController extends Controller
public function yearInReview() public function yearInReview()
{ {
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(config('database.default') != 'mysql', 404);
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
return view('account.yir', compact('profile')); return view('account.yir', compact('profile'));
} }
public function getData(Request $request)
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(config('database.default') != 'mysql', 404);
$uid = $request->user()->id;
$pid = $request->user()->profile_id;
$epoch = '2020-01-01 00:00:00';
$epochStart = '2020-01-01 00:00:00';
$epochEnd = '2020-12-31 23:59:59';
$siteKey = 'seasonal:my2020:shared';
$siteTtl = now()->addMonths(3);
$userKey = 'seasonal:my2020:user:' . $uid;
$userTtl = now()->addMonths(3);
$shared = Cache::remember($siteKey, $siteTtl, function() use($epochStart, $epochEnd) {
return [
'average' => [
'posts' => round(Status::selectRaw('*, count(profile_id) as count')
->whereNull('uri')
->whereIn('type', ['photo','photo:album','video','video:album','photo:video:album'])
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->pluck('count')
->avg()),
'likes' => round(Like::selectRaw('*, count(profile_id) as count')
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->pluck('count')
->avg()),
],
'popular' => [
'hashtag' => StatusHashtag::selectRaw('*,count(hashtag_id) as count')
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('hashtag_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->hashtag->name,
'count' => $sh->count
];
})
->first(),
'post' => Status::whereScope('public')
->where('likes_count', '>', 1)
->whereIsNsfw(false)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->orderByDesc('likes_count')
->take(1)
->get()
->map(function($status) {
return [
'id' => (string) $status->id,
'username' => (string) $status->profile->username,
'created_at' => $status->created_at->format('M d, Y'),
'type' => $status->type,
'url' => $status->url(),
'thumb' => $status->thumb(),
'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count,
'reply_count' => $status->reply_count ?? 0,
];
})
->first(),
'places' => Status::selectRaw('*, count(place_id) as count')
->whereNotNull('place_id')
->having('count', '>', 1)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('place_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->place->getName(),
'url' => $sh->place->url(),
'count' => $sh->count
];
})
->first()
],
];
});
$res = Cache::remember($userKey, $userTtl, function() use($uid, $pid, $epochStart, $epochEnd, $request) {
return [
'account' => [
'user_id' => $request->user()->id,
'created_at' => $request->user()->created_at->format('M d, Y'),
'created_this_year' => $request->user()->created_at->gt('2020-01-01 00:00:00'),
'created_months_ago' => $request->user()->created_at->diffInMonths(now()),
'followers_this_year' => Follower::whereFollowingId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'followed_this_year' => Follower::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'most_popular' => Status::whereProfileId($pid)
->where('likes_count', '>', 1)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->orderByDesc('likes_count')
->take(1)
->get()
->map(function($status) {
return [
'id' => (string) $status->id,
'username' => (string) $status->profile->username,
'created_at' => $status->created_at->format('M d, Y'),
'type' => $status->type,
'url' => $status->url(),
'thumb' => $status->thumb(),
'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count,
'reply_count' => $status->reply_count ?? 0,
];
})
->first(),
'posts_count' => Status::whereProfileId($pid)
->whereIn('type', ['photo','photo:album','video','video:album','photo:video:album'])
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'likes_count' => Like::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'hashtag' => StatusHashtag::selectRaw('*, count(hashtag_id) as count')
->whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->hashtag->name,
'count' => $sh->count
];
})
->first(),
'places' => Status::selectRaw('*, count(place_id) as count')
->whereNotNull('place_id')
->having('count', '>', 1)
->whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('place_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->place->getName(),
'url' => $sh->place->url(),
'count' => $sh->count
];
})
->first(),
'places_total' => Status::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->whereNotNull('place_id')
->count()
]
];
});
return response()->json(array_merge($res, $shared));
}
public function store(Request $request)
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(config('database.default') != 'mysql', 404);
$user = $request->user();
$log = AccountLog::firstOrCreate([
[
'item_type' => 'App\User',
'item_id' => $user->id,
'user_id' => $user->id,
'action' => 'seasonal.my2020.view'
],
[
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent()
]
]);
return response()->json(200);
}
} }

View file

@ -8,6 +8,7 @@ use App\Media;
use App\Profile; use App\Profile;
use App\User; use App\User;
use App\UserFilter; use App\UserFilter;
use App\Util\Lexer\Autolink;
use App\Util\Lexer\PrettyNumber; use App\Util\Lexer\PrettyNumber;
use Auth; use Auth;
use Cache; use Cache;
@ -16,6 +17,7 @@ use Mail;
use Purify; use Purify;
use App\Mail\PasswordChange; use App\Mail\PasswordChange;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\PronounService;
trait HomeSettings trait HomeSettings
{ {
@ -25,13 +27,14 @@ trait HomeSettings
$id = Auth::user()->profile->id; $id = Auth::user()->profile->id;
$storage = []; $storage = [];
$used = Media::whereProfileId($id)->sum('size'); $used = Media::whereProfileId($id)->sum('size');
$storage['limit'] = config('pixelfed.max_account_size') * 1024; $storage['limit'] = config_cache('pixelfed.max_account_size') * 1024;
$storage['used'] = $used; $storage['used'] = $used;
$storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100); $storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100);
$storage['limitPretty'] = PrettyNumber::size($storage['limit']); $storage['limitPretty'] = PrettyNumber::size($storage['limit']);
$storage['usedPretty'] = PrettyNumber::size($storage['used']); $storage['usedPretty'] = PrettyNumber::size($storage['used']);
$pronouns = PronounService::get($id);
return view('settings.home', compact('storage')); return view('settings.home', compact('storage', 'pronouns'));
} }
public function homeUpdate(Request $request) public function homeUpdate(Request $request)
@ -40,7 +43,8 @@ trait HomeSettings
'name' => 'required|string|max:'.config('pixelfed.max_name_length'), 'name' => 'required|string|max:'.config('pixelfed.max_name_length'),
'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'), 'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'),
'website' => 'nullable|url', 'website' => 'nullable|url',
'language' => 'nullable|string|min:2|max:5' 'language' => 'nullable|string|min:2|max:5',
'pronouns' => 'nullable|array|max:4'
]); ]);
$changes = false; $changes = false;
@ -50,12 +54,14 @@ trait HomeSettings
$language = $request->input('language'); $language = $request->input('language');
$user = Auth::user(); $user = Auth::user();
$profile = $user->profile; $profile = $user->profile;
$pronouns = $request->input('pronouns');
$existingPronouns = PronounService::get($profile->id);
$layout = $request->input('profile_layout'); $layout = $request->input('profile_layout');
if($layout) { if($layout) {
$layout = !in_array($layout, ['metro', 'moment']) ? 'metro' : $layout; $layout = !in_array($layout, ['metro', 'moment']) ? 'metro' : $layout;
} }
$enforceEmailVerification = config('pixelfed.enforce_email_verification'); $enforceEmailVerification = config_cache('pixelfed.enforce_email_verification');
// Only allow email to be updated if not yet verified // Only allow email to be updated if not yet verified
if (!$enforceEmailVerification || !$changes && $user->email_verified_at) { if (!$enforceEmailVerification || !$changes && $user->email_verified_at) {
@ -70,9 +76,9 @@ trait HomeSettings
$profile->website = $website; $profile->website = $website;
} }
if ($profile->bio != $bio) { if (strip_tags($profile->bio) != $bio) {
$changes = true; $changes = true;
$profile->bio = $bio; $profile->bio = Autolink::create()->autolink($bio);
} }
if($user->language != $language && if($user->language != $language &&
@ -82,6 +88,14 @@ trait HomeSettings
$user->language = $language; $user->language = $language;
session()->put('locale', $language); session()->put('locale', $language);
} }
if($existingPronouns != $pronouns) {
if($pronouns && in_array('Select Pronoun(s)', $pronouns)) {
PronounService::clear($profile->id);
} else {
PronounService::put($profile->id, $pronouns);
}
}
} }
if ($changes === true) { if ($changes === true) {
@ -152,7 +166,7 @@ trait HomeSettings
$user = Auth::user(); $user = Auth::user();
$profile = $user->profile; $profile = $user->profile;
$validate = config('pixelfed.enforce_email_verification'); $validate = config_cache('pixelfed.enforce_email_verification');
if ($user->email != $email) { if ($user->email != $email) {
$changes = true; $changes = true;

View file

@ -74,6 +74,11 @@ trait PrivacySettings
} }
Cache::forget('profile:settings:' . $profile->id); Cache::forget('profile:settings:' . $profile->id);
Cache::forget('user:account:id:' . $profile->user_id); Cache::forget('user:account:id:' . $profile->user_id);
Cache::forget('profile:follower_count:' . $profile->id);
Cache::forget('profile:following_count:' . $profile->id);
Cache::forget('profile:embed:' . $profile->id);
Cache::forget('pf:acct:settings:hidden-followers:' . $profile->id);
Cache::forget('pf:acct:settings:hidden-following:' . $profile->id);
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!'); return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
} }

View file

@ -7,7 +7,9 @@ use App\Following;
use App\ProfileSponsor; use App\ProfileSponsor;
use App\Report; use App\Report;
use App\UserFilter; use App\UserFilter;
use App\UserSetting;
use Auth, Cookie, DB, Cache, Purify; use Auth, Cookie, DB, Cache, Purify;
use Illuminate\Support\Facades\Redis;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -20,6 +22,7 @@ use App\Http\Controllers\Settings\{
SecuritySettings SecuritySettings
}; };
use App\Jobs\DeletePipeline\DeleteAccountPipeline; use App\Jobs\DeletePipeline\DeleteAccountPipeline;
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
class SettingsController extends Controller class SettingsController extends Controller
{ {
@ -77,13 +80,13 @@ class SettingsController extends Controller
public function dataImport() public function dataImport()
{ {
abort_if(!config('pixelfed.import.instagram.enabled'), 404); abort_if(!config_cache('pixelfed.import.instagram.enabled'), 404);
return view('settings.import.home'); return view('settings.import.home');
} }
public function dataImportInstagram() public function dataImportInstagram()
{ {
abort_if(!config('pixelfed.import.instagram.enabled'), 404); abort_if(!config_cache('pixelfed.import.instagram.enabled'), 404);
return view('settings.import.instagram.home'); return view('settings.import.instagram.home');
} }
@ -151,13 +154,6 @@ class SettingsController extends Controller
return view('settings.export.show'); return view('settings.export.show');
} }
public function reportsHome(Request $request)
{
$profile = Auth::user()->profile;
$reports = Report::whereProfileId($profile->id)->orderByDesc('created_at')->paginate(10);
return view('settings.reports', compact('reports'));
}
public function metroDarkMode(Request $request) public function metroDarkMode(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
@ -227,7 +223,97 @@ class SettingsController extends Controller
$sponsors->sponsors = json_encode($res); $sponsors->sponsors = json_encode($res);
$sponsors->save(); $sponsors->save();
$sponsors = $res; $sponsors = $res;
return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');; return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');
}
public function timelineSettings(Request $request)
{
$pid = $request->user()->profile_id;
$top = Redis::zscore('pf:tl:top', $pid) != false;
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
return view('settings.timeline', compact('top', 'replies'));
}
public function updateTimelineSettings(Request $request)
{
$pid = $request->user()->profile_id;
$top = $request->has('top') && $request->input('top') === 'on';
$replies = $request->has('replies') && $request->input('replies') === 'on';
if($top) {
Redis::zadd('pf:tl:top', $pid, $pid);
} else {
Redis::zrem('pf:tl:top', $pid);
}
if($replies) {
Redis::zadd('pf:tl:replies', $pid, $pid);
} else {
Redis::zrem('pf:tl:replies', $pid);
}
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');;
}
public function mediaSettings(Request $request)
{
$setting = UserSetting::whereUserId($request->user()->id)->firstOrFail();
$compose = $setting->compose_settings ? json_decode($setting->compose_settings, true) : [
'default_license' => null,
'media_descriptions' => false
];
return view('settings.media', compact('compose'));
}
public function updateMediaSettings(Request $request)
{
$this->validate($request, [
'default' => 'required|int|min:1|max:16',
'sync' => 'nullable',
'media_descriptions' => 'nullable'
]);
$license = $request->input('default');
$sync = $request->input('sync') == 'on';
$media_descriptions = $request->input('media_descriptions') == 'on';
$uid = $request->user()->id;
$setting = UserSetting::whereUserId($uid)->firstOrFail();
$compose = json_decode($setting->compose_settings, true);
$changed = false;
if($sync) {
$key = 'pf:settings:mls_recently:'.$uid;
if(Cache::get($key) == 2) {
$msg = 'You can only sync licenses twice per 24 hours. Try again later.';
return redirect(route('settings'))
->with('error', $msg);
}
}
if(!isset($compose['default_license']) || $compose['default_license'] !== $license) {
$compose['default_license'] = (int) $license;
$changed = true;
}
if(!isset($compose['media_descriptions']) || $compose['media_descriptions'] !== $media_descriptions) {
$compose['media_descriptions'] = $media_descriptions;
$changed = true;
}
if($changed) {
$setting->compose_settings = json_encode($compose);
$setting->save();
Cache::forget('profile:compose:settings:' . $request->user()->id);
}
if($sync) {
$val = Cache::has($key) ? 2 : 1;
Cache::put($key, $val, 86400);
MediaSyncLicensePipeline::dispatch($uid, $license);
return redirect(route('settings'))->with('status', 'Media licenses successfully synced! It may take a few minutes to take effect for every post.');
}
return redirect(route('settings'))->with('status', 'Media settings successfully updated!');
} }
} }

View file

@ -9,6 +9,7 @@ use App\Util\Lexer\PrettyNumber;
use App\{Follower, Page, Profile, Status, User, UserFilter}; use App\{Follower, Page, Profile, Status, User, UserFilter};
use App\Util\Localization\Localization; use App\Util\Localization\Localization;
use App\Services\FollowerService; use App\Services\FollowerService;
use App\Util\ActivityPub\Helpers;
class SiteController extends Controller class SiteController extends Controller
{ {
@ -23,16 +24,7 @@ class SiteController extends Controller
public function homeGuest() public function homeGuest()
{ {
$data = Cache::remember('site:landing:data', now()->addHours(3), function() { return view('site.index');
return [
'stats' => [
'posts' => App\Util\Lexer\PrettyNumber::convert(App\Status::count()),
'likes' => App\Util\Lexer\PrettyNumber::convert(App\Like::count()),
'hashtags' => App\Util\Lexer\PrettyNumber::convert(App\StatusHashtag::count())
],
];
});
return view('site.index', compact('data'));
} }
public function homeTimeline(Request $request) public function homeTimeline(Request $request)
@ -62,16 +54,12 @@ class SiteController extends Controller
public function about() public function about()
{ {
$page = Page::whereSlug('/site/about')->whereActive(true)->first(); return Cache::remember('site.about_v2', now()->addMinutes(15), function() {
$stats = Cache::remember('site:about:stats-v1', now()->addHours(12), function() { $user_count = number_format(User::count());
return [ $post_count = number_format(Status::count());
'posts' => Status::count(), $rules = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : null;
'users' => User::whereNull('status')->count(), return view('site.about', compact('rules', 'user_count', 'post_count'))->render();
'admin' => User::whereIsAdmin(true)->first()
];
}); });
$path = $page ? 'site.about-custom' : 'site.about';
return view($path, compact('page', 'stats'));
} }
public function language() public function language()
@ -108,10 +96,12 @@ class SiteController extends Controller
public function redirectUrl(Request $request) public function redirectUrl(Request $request)
{ {
abort_if(!$request->user(), 404);
$this->validate($request, [ $this->validate($request, [
'url' => 'required|url' 'url' => 'required|url'
]); ]);
$url = request()->input('url'); $url = request()->input('url');
abort_if(Helpers::validateUrl($url) == false, 404);
return view('site.redirect', compact('url')); return view('site.redirect', compact('url'));
} }
@ -149,4 +139,19 @@ class SiteController extends Controller
return redirect($url); return redirect($url);
} }
public function legacyWebfingerRedirect(Request $request, $username, $domain)
{
$un = '@'.$username.'@'.$domain;
$profile = Profile::whereUsername($un)
->firstOrFail();
if($profile->domain == null) {
$url = "/$profile->username";
} else {
$url = $request->user() ? "/i/web/profile/_/{$profile->id}" : $profile->url();
}
return redirect($url);
}
} }

View file

@ -0,0 +1,128 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Cache;
use DB;
use League\CommonMark\CommonMarkConverter;
use App\Services\AccountService;
use App\Services\StatusService;
use App\Services\SnowflakeService;
use App\Util\Localization\Localization;
class SpaController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
abort_unless(config('exp.spa'), 404);
return view('layouts.spa');
}
public function webPost(Request $request, $id)
{
abort_unless(config('exp.spa'), 404);
if($request->user()) {
return view('layouts.spa');
}
if(SnowflakeService::byDate(now()->subDays(30)) > $id) {
abort(404);
}
$post = StatusService::get($id);
if(
$post &&
isset($post['url']) &&
isset($post['local']) &&
$post['local'] === true
) {
return redirect($post['url']);
}
abort(404);
}
public function webProfile(Request $request, $id)
{
abort_unless(config('exp.spa'), 404);
if($request->user()) {
if(substr($id, 0, 1) == '@') {
$id = AccountService::usernameToId(substr($id, 1));
return redirect("/i/web/profile/{$id}");
}
return view('layouts.spa');
}
$account = AccountService::get($id);
if($account && isset($account['url'])) {
return redirect($account['url']);
}
return redirect('404');
}
public function updateLanguage(Request $request)
{
$this->validate($request, [
'v' => 'required|in:0.1,0.2',
'l' => 'required|alpha_dash|max:5'
]);
$lang = $request->input('l');
$user = $request->user();
abort_if(!in_array($lang, Localization::languages()), 400);
$user->language = $lang;
$user->save();
session()->put('locale', $lang);
return ['language' => $lang];
}
public function getPrivacy()
{
$body = $this->markdownToHtml('views/page/privacy.md');
return [
'body' => $body
];
}
public function getTerms()
{
$body = $this->markdownToHtml('views/page/terms.md');
return [
'body' => $body
];
}
protected function markdownToHtml($src, $ttl = 600)
{
return Cache::remember(
'pf:doc_cache:markdown:' . $src,
$ttl,
function() use($src) {
$path = resource_path($src);
$file = file_get_contents($path);
$converter = new CommonMarkConverter();
return (string) $converter->convertToHtml($file);
});
}
public function usernameRedirect(Request $request, $username)
{
$id = AccountService::usernameToId($username);
if(!$id) {
return redirect('/i/web/404');
}
return redirect('/i/web/profile/' . $id);
}
}

View file

@ -6,24 +6,30 @@ use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Jobs\StatusPipeline\StatusDelete; use App\Jobs\StatusPipeline\StatusDelete;
use App\Jobs\SharePipeline\SharePipeline; use App\Jobs\SharePipeline\SharePipeline;
use App\Jobs\SharePipeline\UndoSharePipeline;
use App\AccountInterstitial; use App\AccountInterstitial;
use App\Media; use App\Media;
use App\Profile; use App\Profile;
use App\Status; use App\Status;
use App\StatusArchived;
use App\StatusView; use App\StatusView;
use App\Transformer\ActivityPub\StatusTransformer; use App\Transformer\ActivityPub\StatusTransformer;
use App\Transformer\ActivityPub\Verb\Note; use App\Transformer\ActivityPub\Verb\Note;
use App\Transformer\ActivityPub\Verb\Question;
use App\User; use App\User;
use Auth, Cache; use Auth, DB, Cache;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use League\Fractal; use League\Fractal;
use App\Util\Media\Filter; use App\Util\Media\Filter;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Services\HashidService; use App\Services\HashidService;
use App\Services\StatusService;
use App\Util\Media\License;
use App\Services\ReblogService;
class StatusController extends Controller class StatusController extends Controller
{ {
public function show(Request $request, $username, int $id) public function show(Request $request, $username, $id)
{ {
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
@ -68,7 +74,7 @@ class StatusController extends Controller
]); ]);
} }
if ($request->wantsJson() && config('federation.activitypub.enabled')) { if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
return $this->showActivityPub($request, $status); return $this->showActivityPub($request, $status);
} }
@ -78,16 +84,7 @@ class StatusController extends Controller
public function shortcodeRedirect(Request $request, $id) public function shortcodeRedirect(Request $request, $id)
{ {
abort_if(strlen($id) < 5, 404); abort(404);
if(!Auth::check()) {
return redirect('/login?next='.urlencode('/' . $request->path()));
}
$id = HashidService::decode($id);
$status = Status::find($id);
if(!$status) {
return redirect('/404');
}
return redirect($status->url());
} }
public function showId(int $id) public function showId(int $id)
@ -211,6 +208,8 @@ class StatusController extends Controller
Cache::forget('_api:statuses:recent_9:' . $status->profile_id); Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
Cache::forget('profile:status_count:' . $status->profile_id); Cache::forget('profile:status_count:' . $status->profile_id);
Cache::forget('profile:embed:' . $status->profile_id);
StatusService::del($status->id, true);
if ($status->profile_id == $user->profile->id || $user->is_admin == true) { if ($status->profile_id == $user->profile->id || $user->is_admin == true) {
Cache::forget('profile:status_count:'.$status->profile_id); Cache::forget('profile:status_count:'.$status->profile_id);
StatusDelete::dispatch($status); StatusDelete::dispatch($status);
@ -233,21 +232,21 @@ class StatusController extends Controller
$user = Auth::user(); $user = Auth::user();
$profile = $user->profile; $profile = $user->profile;
$status = Status::withCount('shares') $status = Status::whereScope('public')
->whereIn('scope', ['public', 'unlisted'])
->findOrFail($request->input('item')); ->findOrFail($request->input('item'));
$count = $status->shares()->count(); $count = $status->reblogs_count;
$exists = Status::whereProfileId(Auth::user()->profile->id) $exists = Status::whereProfileId(Auth::user()->profile->id)
->whereReblogOfId($status->id) ->whereReblogOfId($status->id)
->count(); ->exists();
if ($exists !== 0) { if ($exists == true) {
$shares = Status::whereProfileId(Auth::user()->profile->id) $shares = Status::whereProfileId(Auth::user()->profile->id)
->whereReblogOfId($status->id) ->whereReblogOfId($status->id)
->get(); ->get();
foreach ($shares as $share) { foreach ($shares as $share) {
$share->delete(); UndoSharePipeline::dispatch($share);
ReblogService::del($profile->id, $status->id);
$count--; $count--;
} }
} else { } else {
@ -258,14 +257,11 @@ class StatusController extends Controller
$share->save(); $share->save();
$count++; $count++;
SharePipeline::dispatch($share); SharePipeline::dispatch($share);
} ReblogService::add($profile->id, $status->id);
if($count >= 0) {
$status->reblogs_count = $count;
$status->save();
} }
Cache::forget('status:'.$status->id.':sharedby:userid:'.$user->id); Cache::forget('status:'.$status->id.':sharedby:userid:'.$user->id);
StatusService::del($status->id);
if ($request->ajax()) { if ($request->ajax()) {
$response = ['code' => 200, 'msg' => 'Share saved', 'count' => $count]; $response = ['code' => 200, 'msg' => 'Share saved', 'count' => $count];
@ -278,11 +274,12 @@ class StatusController extends Controller
public function showActivityPub(Request $request, $status) public function showActivityPub(Request $request, $status)
{ {
$object = $status->type == 'poll' ? new Question() : new Note();
$fractal = new Fractal\Manager(); $fractal = new Fractal\Manager();
$resource = new Fractal\Resource\Item($status, new Note()); $resource = new Fractal\Resource\Item($status, $object);
$res = $fractal->createData($resource)->toArray(); $res = $fractal->createData($resource)->toArray();
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT); return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
} }
public function edit(Request $request, $username, $id) public function edit(Request $request, $username, $id)
@ -291,9 +288,9 @@ class StatusController extends Controller
$user = Auth::user()->profile; $user = Auth::user()->profile;
$status = Status::whereProfileId($user->id) $status = Status::whereProfileId($user->id)
->with(['media']) ->with(['media'])
->where('created_at', '>', now()->subHours(24))
->findOrFail($id); ->findOrFail($id);
return view('status.edit', compact('user', 'status')); $licenses = License::get();
return view('status.edit', compact('user', 'status', 'licenses'));
} }
public function editStore(Request $request, $username, $id) public function editStore(Request $request, $username, $id)
@ -302,41 +299,21 @@ class StatusController extends Controller
$user = Auth::user()->profile; $user = Auth::user()->profile;
$status = Status::whereProfileId($user->id) $status = Status::whereProfileId($user->id)
->with(['media']) ->with(['media'])
->where('created_at', '>', now()->subHours(24))
->findOrFail($id); ->findOrFail($id);
$this->validate($request, [ $this->validate($request, [
'id' => 'required|integer|min:1', 'license' => 'nullable|integer|min:1|max:16',
'caption' => 'nullable',
'filter' => 'nullable|alpha_dash|max:30',
]); ]);
$id = $request->input('id'); $licenseId = $request->input('license');
$caption = $request->input('caption');
$filter = $request->input('filter');
$media = Media::whereProfileId($user->id) $status->media->each(function($media) use($licenseId) {
->whereStatusId($status->id) $media->license = $licenseId;
->findOrFail($id);
$changed = false;
if ($media->caption != $caption) {
$media->caption = $caption;
$changed = true;
}
if ($media->filter_class != $filter && in_array($filter, Filter::classes())) {
$media->filter_class = $filter;
$changed = true;
}
if ($changed === true) {
$media->save(); $media->save();
Cache::forget('status:transformer:media:attachments:'.$media->status_id); Cache::forget('status:transformer:media:attachments:'.$media->status_id);
} });
return response()->json([], 200); return redirect($status->url());
} }
protected function authCheck() protected function authCheck()
@ -354,7 +331,7 @@ class StatusController extends Controller
public static function mimeTypeCheck($mimes) public static function mimeTypeCheck($mimes)
{ {
$allowed = explode(',', config('pixelfed.media_types')); $allowed = explode(',', config_cache('pixelfed.media_types'));
$count = count($mimes); $count = count($mimes);
$photos = 0; $photos = 0;
$videos = 0; $videos = 0;
@ -384,6 +361,8 @@ class StatusController extends Controller
if($photos >= 1 && $videos >= 1) { if($photos >= 1 && $videos >= 1) {
return 'photo:video:album'; return 'photo:video:album';
} }
return 'text';
} }
public function toggleVisibility(Request $request) { public function toggleVisibility(Request $request) {
@ -408,4 +387,33 @@ class StatusController extends Controller
return response()->json([200]); return response()->json([200]);
} }
public function storeView(Request $request)
{
abort_if(!$request->user(), 403);
$views = $request->input('_v');
$uid = $request->user()->profile_id;
if(empty($views) || !is_array($views)) {
return response()->json(0);
}
Cache::forget('profile:home-timeline-cursor:' . $request->user()->id);
foreach($views as $view) {
if(!isset($view['sid']) || !isset($view['pid'])) {
continue;
}
DB::transaction(function () use($view, $uid) {
StatusView::firstOrCreate([
'status_id' => $view['sid'],
'status_profile_id' => $view['pid'],
'profile_id' => $uid
]);
});
}
return response()->json(1);
}
} }

View file

@ -0,0 +1,501 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Media;
use App\Profile;
use App\Report;
use App\DirectMessage;
use App\Notification;
use App\Status;
use App\Story;
use App\StoryView;
use App\Models\Poll;
use App\Models\PollVote;
use App\Services\ProfileService;
use App\Services\StoryService;
use Cache, Storage;
use Image as Intervention;
use App\Services\FollowerService;
use App\Services\MediaPathService;
use FFMpeg;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Format\Video\X264;
use App\Jobs\StoryPipeline\StoryReactionDeliver;
use App\Jobs\StoryPipeline\StoryReplyDeliver;
use App\Jobs\StoryPipeline\StoryFanout;
use App\Jobs\StoryPipeline\StoryDelete;
use ImageOptimizer;
class StoryComposeController extends Controller
{
public function apiV1Add(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'file' => function() {
return [
'required',
'mimes:image/jpeg,image/png,video/mp4',
'max:' . config_cache('pixelfed.max_photo_size'),
];
},
]);
$user = $request->user();
$count = Story::whereProfileId($user->profile_id)
->whereActive(true)
->where('expires_at', '>', now())
->count();
if($count >= Story::MAX_PER_DAY) {
abort(418, 'You have reached your limit for new Stories today.');
}
$photo = $request->file('file');
$path = $this->storePhoto($photo, $user);
$story = new Story();
$story->duration = 3;
$story->profile_id = $user->profile_id;
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo';
$story->mime = $photo->getMimeType();
$story->path = $path;
$story->local = true;
$story->size = $photo->getSize();
$story->bearcap_token = str_random(64);
$story->expires_at = now()->addMinutes(1440);
$story->save();
$url = $story->path;
$res = [
'code' => 200,
'msg' => 'Successfully added',
'media_id' => (string) $story->id,
'media_url' => url(Storage::url($url)) . '?v=' . time(),
'media_type' => $story->type
];
if($story->type === 'video') {
$video = FFMpeg::open($path);
$duration = $video->getDurationInSeconds();
$res['media_duration'] = $duration;
if($duration > 500) {
Storage::delete($story->path);
$story->delete();
return response()->json([
'message' => 'Video duration cannot exceed 60 seconds'
], 422);
}
}
return $res;
}
protected function storePhoto($photo, $user)
{
$mimes = explode(',', config_cache('pixelfed.media_types'));
if(in_array($photo->getMimeType(), [
'image/jpeg',
'image/png',
'video/mp4'
]) == false) {
abort(400, 'Invalid media type');
return;
}
$storagePath = MediaPathService::story($user->profile);
$path = $photo->storeAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension());
if(in_array($photo->getMimeType(), ['image/jpeg','image/png'])) {
$fpath = storage_path('app/' . $path);
$img = Intervention::make($fpath);
$img->orientate();
$img->save($fpath, config_cache('pixelfed.image_quality'));
$img->destroy();
}
return $path;
}
public function cropPhoto(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'media_id' => 'required|integer|min:1',
'width' => 'required',
'height' => 'required',
'x' => 'required',
'y' => 'required'
]);
$user = $request->user();
$id = $request->input('media_id');
$width = round($request->input('width'));
$height = round($request->input('height'));
$x = round($request->input('x'));
$y = round($request->input('y'));
$story = Story::whereProfileId($user->profile_id)->findOrFail($id);
$path = storage_path('app/' . $story->path);
if(!is_file($path)) {
abort(400, 'Invalid or missing media.');
}
if($story->type === 'photo') {
$img = Intervention::make($path);
$img->crop($width, $height, $x, $y);
$img->resize(1080, 1920, function ($constraint) {
$constraint->aspectRatio();
});
$img->save($path, config_cache('pixelfed.image_quality'));
}
return [
'code' => 200,
'msg' => 'Successfully cropped',
];
}
public function publishStory(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'media_id' => 'required',
'duration' => 'required|integer|min:3|max:120',
'can_reply' => 'required|boolean',
'can_react' => 'required|boolean'
]);
$id = $request->input('media_id');
$user = $request->user();
$story = Story::whereProfileId($user->profile_id)
->findOrFail($id);
$story->active = true;
$story->duration = $request->input('duration', 10);
$story->can_reply = $request->input('can_reply');
$story->can_react = $request->input('can_react');
$story->save();
StoryService::delLatest($story->profile_id);
StoryFanout::dispatch($story)->onQueue('story');
StoryService::addRotateQueue($story->id);
return [
'code' => 200,
'msg' => 'Successfully published',
];
}
public function apiV1Delete(Request $request, $id)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$user = $request->user();
$story = Story::whereProfileId($user->profile_id)
->findOrFail($id);
$story->active = false;
$story->save();
StoryDelete::dispatch($story)->onQueue('story');
return [
'code' => 200,
'msg' => 'Successfully deleted'
];
}
public function compose(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
return view('stories.compose');
}
public function createPoll(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
abort_if(!config_cache('instance.polls.enabled'), 404);
return $request->all();
}
public function publishStoryPoll(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'question' => 'required|string|min:6|max:140',
'options' => 'required|array|min:2|max:4',
'can_reply' => 'required|boolean',
'can_react' => 'required|boolean'
]);
$pid = $request->user()->profile_id;
$count = Story::whereProfileId($pid)
->whereActive(true)
->where('expires_at', '>', now())
->count();
if($count >= Story::MAX_PER_DAY) {
abort(418, 'You have reached your limit for new Stories today.');
}
$story = new Story;
$story->type = 'poll';
$story->story = json_encode([
'question' => $request->input('question'),
'options' => $request->input('options')
]);
$story->public = false;
$story->local = true;
$story->profile_id = $pid;
$story->expires_at = now()->addMinutes(1440);
$story->duration = 30;
$story->can_reply = false;
$story->can_react = false;
$story->save();
$poll = new Poll;
$poll->story_id = $story->id;
$poll->profile_id = $pid;
$poll->poll_options = $request->input('options');
$poll->expires_at = $story->expires_at;
$poll->cached_tallies = collect($poll->poll_options)->map(function($o) {
return 0;
})->toArray();
$poll->save();
$story->active = true;
$story->save();
StoryService::delLatest($story->profile_id);
return [
'code' => 200,
'msg' => 'Successfully published',
];
}
public function storyPollVote(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'sid' => 'required',
'ci' => 'required|integer|min:0|max:3'
]);
$pid = $request->user()->profile_id;
$ci = $request->input('ci');
$story = Story::findOrFail($request->input('sid'));
abort_if(!FollowerService::follows($pid, $story->profile_id), 403);
$poll = Poll::whereStoryId($story->id)->firstOrFail();
$vote = new PollVote;
$vote->profile_id = $pid;
$vote->poll_id = $poll->id;
$vote->story_id = $story->id;
$vote->status_id = null;
$vote->choice = $ci;
$vote->save();
$poll->votes_count = $poll->votes_count + 1;
$poll->cached_tallies = collect($poll->getTallies())->map(function($tally, $key) use($ci) {
return $ci == $key ? $tally + 1 : $tally;
})->toArray();
$poll->save();
return 200;
}
public function storeReport(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'type' => 'required|alpha_dash',
'id' => 'required|integer|min:1',
]);
$pid = $request->user()->profile_id;
$sid = $request->input('id');
$type = $request->input('type');
$types = [
// original 3
'spam',
'sensitive',
'abusive',
// new
'underage',
'copyright',
'impersonation',
'scam',
'terrorism'
];
abort_if(!in_array($type, $types), 422, 'Invalid story report type');
$story = Story::findOrFail($sid);
abort_if($story->profile_id == $pid, 422, 'Cannot report your own story');
abort_if(!FollowerService::follows($pid, $story->profile_id), 422, 'Cannot report a story from an account you do not follow');
if( Report::whereProfileId($pid)
->whereObjectType('App\Story')
->whereObjectId($story->id)
->exists()
) {
return response()->json(['error' => [
'code' => 409,
'message' => 'Cannot report the same story again'
]], 409);
}
$report = new Report;
$report->profile_id = $pid;
$report->user_id = $request->user()->id;
$report->object_id = $story->id;
$report->object_type = 'App\Story';
$report->reported_profile_id = $story->profile_id;
$report->type = $type;
$report->message = null;
$report->save();
return [200];
}
public function react(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'sid' => 'required',
'reaction' => 'required|string'
]);
$pid = $request->user()->profile_id;
$text = $request->input('reaction');
$story = Story::findOrFail($request->input('sid'));
abort_if(!$story->can_react, 422);
abort_if(StoryService::reactCounter($story->id, $pid) >= 5, 422, 'You have already reacted to this story');
$status = new Status;
$status->profile_id = $pid;
$status->type = 'story:reaction';
$status->caption = $text;
$status->rendered = $text;
$status->scope = 'direct';
$status->visibility = 'direct';
$status->in_reply_to_profile_id = $story->profile_id;
$status->entities = json_encode([
'story_id' => $story->id,
'reaction' => $text
]);
$status->save();
$dm = new DirectMessage;
$dm->to_id = $story->profile_id;
$dm->from_id = $pid;
$dm->type = 'story:react';
$dm->status_id = $status->id;
$dm->meta = json_encode([
'story_username' => $story->profile->username,
'story_actor_username' => $request->user()->username,
'story_id' => $story->id,
'story_media_url' => url(Storage::url($story->path)),
'reaction' => $text
]);
$dm->save();
if($story->local) {
// generate notification
$n = new Notification;
$n->profile_id = $dm->to_id;
$n->actor_id = $dm->from_id;
$n->item_id = $dm->id;
$n->item_type = 'App\DirectMessage';
$n->action = 'story:react';
$n->message = "{$request->user()->username} reacted to your story";
$n->rendered = "{$request->user()->username} reacted to your story";
$n->save();
} else {
StoryReactionDeliver::dispatch($story, $status)->onQueue('story');
}
StoryService::reactIncrement($story->id, $pid);
return 200;
}
public function comment(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'sid' => 'required',
'caption' => 'required|string'
]);
$pid = $request->user()->profile_id;
$text = $request->input('caption');
$story = Story::findOrFail($request->input('sid'));
abort_if(!$story->can_reply, 422);
$status = new Status;
$status->type = 'story:reply';
$status->profile_id = $pid;
$status->caption = $text;
$status->rendered = $text;
$status->scope = 'direct';
$status->visibility = 'direct';
$status->in_reply_to_profile_id = $story->profile_id;
$status->entities = json_encode([
'story_id' => $story->id
]);
$status->save();
$dm = new DirectMessage;
$dm->to_id = $story->profile_id;
$dm->from_id = $pid;
$dm->type = 'story:comment';
$dm->status_id = $status->id;
$dm->meta = json_encode([
'story_username' => $story->profile->username,
'story_actor_username' => $request->user()->username,
'story_id' => $story->id,
'story_media_url' => url(Storage::url($story->path)),
'caption' => $text
]);
$dm->save();
if($story->local) {
// generate notification
$n = new Notification;
$n->profile_id = $dm->to_id;
$n->actor_id = $dm->from_id;
$n->item_id = $dm->id;
$n->item_type = 'App\DirectMessage';
$n->action = 'story:comment';
$n->message = "{$request->user()->username} commented on story";
$n->rendered = "{$request->user()->username} commented on story";
$n->save();
} else {
StoryReplyDeliver::dispatch($story, $status)->onQueue('story');
}
return 200;
}
}

View file

@ -4,248 +4,128 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\DirectMessage;
use App\Follower;
use App\Notification;
use App\Media; use App\Media;
use App\Profile; use App\Profile;
use App\Status;
use App\Story; use App\Story;
use App\StoryView; use App\StoryView;
use App\Services\PollService;
use App\Services\ProfileService;
use App\Services\StoryService; use App\Services\StoryService;
use Cache, Storage; use Cache, Storage;
use Image as Intervention; use Image as Intervention;
use App\Services\AccountService;
use App\Services\FollowerService; use App\Services\FollowerService;
use App\Services\MediaPathService;
use FFMpeg;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Format\Video\X264;
use League\Fractal\Manager;
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Resource\Item;
use App\Transformer\ActivityPub\Verb\StoryVerb;
use App\Jobs\StoryPipeline\StoryViewDeliver;
class StoryController extends StoryComposeController
class StoryController extends Controller
{ {
public function apiV1Add(Request $request) public function recent(Request $request)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$pid = $request->user()->profile_id;
$this->validate($request, [
'file' => function() {
return [
'required',
'mimes:image/jpeg,image/png,video/mp4',
'max:' . config('pixelfed.max_photo_size'),
];
},
]);
$user = $request->user();
if(Story::whereProfileId($user->profile_id)->where('expires_at', '>', now())->count() >= Story::MAX_PER_DAY) {
abort(400, 'You have reached your limit for new Stories today.');
}
$photo = $request->file('file');
$path = $this->storePhoto($photo);
$story = new Story();
$story->duration = 3;
$story->profile_id = $user->profile_id;
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo';
$story->mime = $photo->getMimeType();
$story->path = $path;
$story->local = true;
$story->size = $photo->getSize();
$story->expires_at = now()->addHours(24);
$story->save();
return [
'code' => 200,
'msg' => 'Successfully added',
'media_url' => url(Storage::url($story->path))
];
}
protected function storePhoto($photo)
{
$monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12);
$sid = (string) Str::uuid();
$rid = Str::random(9).'.'.Str::random(9);
$mimes = explode(',', config('pixelfed.media_types'));
if(in_array($photo->getMimeType(), [
'image/jpeg',
'image/png',
'video/mp4'
]) == false) {
abort(400, 'Invalid media type');
return;
}
$storagePath = "public/_esm.t2/{$monthHash}/{$sid}/{$rid}";
$path = $photo->store($storagePath);
if(in_array($photo->getMimeType(), ['image/jpeg','image/png',])) {
$fpath = storage_path('app/' . $path);
$img = Intervention::make($fpath);
$img->orientate();
$img->save($fpath, config('pixelfed.image_quality'));
$img->destroy();
}
return $path;
}
public function apiV1Delete(Request $request, $id)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$user = $request->user();
$story = Story::whereProfileId($user->profile_id)
->findOrFail($id);
if(Storage::exists($story->path) == true) {
Storage::delete($story->path);
}
$story->delete();
return [
'code' => 200,
'msg' => 'Successfully deleted'
];
}
public function apiV1Recent(Request $request)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$profile = $request->user()->profile;
$following = $profile->following->pluck('id')->toArray();
if(config('database.default') == 'pgsql') { if(config('database.default') == 'pgsql') {
$db = Story::with('profile') $s = Story::select('stories.*', 'followers.following_id')
->whereIn('profile_id', $following) ->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
->where('expires_at', '>', now()) ->where('followers.profile_id', $pid)
->distinct('profile_id') ->where('stories.active', true)
->take(9)
->get();
} else {
$db = Story::with('profile')
->whereIn('profile_id', $following)
->where('expires_at', '>', now())
->orderByDesc('expires_at')
->groupBy('profile_id')
->take(9)
->get();
}
$stories = $db->map(function($s, $k) {
return [
'id' => (string) $s->id,
'photo' => $s->profile->avatarUrl(),
'name' => $s->profile->username,
'link' => $s->profile->url(),
'lastUpdated' => (int) $s->created_at->format('U'),
'seen' => $s->seen(),
'items' => [],
'pid' => (string) $s->profile->id
];
});
return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
public function apiV1Fetch(Request $request, $id)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$authed = $request->user()->profile;
$profile = Profile::findOrFail($id);
if($id == $authed->id) {
$publicOnly = true;
} else {
$publicOnly = (bool) $profile->followedBy($authed);
}
$stories = Story::whereProfileId($profile->id)
->orderBy('expires_at', 'desc')
->where('expires_at', '>', now())
->when(!$publicOnly, function($query, $publicOnly) {
return $query->wherePublic(true);
})
->get() ->get()
->map(function($s, $k) { ->map(function($s) {
return [ $r = new \StdClass;
'id' => (string) $s->id, $r->id = $s->id;
'type' => Str::endsWith($s->path, '.mp4') ? 'video' :'photo', $r->profile_id = $s->profile_id;
'length' => 3, $r->type = $s->type;
'src' => url(Storage::url($s->path)), $r->path = $s->path;
'preview' => null, return $r;
'link' => null, })
'linkText' => null, ->unique('profile_id');
'time' => $s->created_at->format('U'),
'expires_at' => (int) $s->expires_at->format('U'),
'seen' => $s->seen()
];
})->toArray();
return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
public function apiV1Item(Request $request, $id)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$authed = $request->user()->profile;
$story = Story::with('profile')
->where('expires_at', '>', now())
->findOrFail($id);
$profile = $story->profile;
if($story->profile_id == $authed->id) {
$publicOnly = true;
} else { } else {
$publicOnly = (bool) $profile->followedBy($authed); $s = Story::select('stories.*', 'followers.following_id')
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
->where('followers.profile_id', $pid)
->where('stories.active', true)
->groupBy('followers.following_id')
->orderByDesc('id')
->get();
} }
abort_if(!$publicOnly, 403); $res = $s->map(function($s) use($pid) {
$profile = AccountService::get($s->profile_id);
$res = [ $url = $profile['local'] ? url("/stories/{$profile['username']}") :
'id' => (string) $story->id, url("/i/rs/{$profile['id']}");
'type' => Str::endsWith($story->path, '.mp4') ? 'video' :'photo', return [
'length' => 3, 'pid' => $profile['id'],
'src' => url(Storage::url($story->path)), 'avatar' => $profile['avatar'],
'preview' => null, 'local' => $profile['local'],
'link' => null, 'username' => $profile['acct'],
'linkText' => null, 'latest' => [
'time' => $story->created_at->format('U'), 'id' => $s->id,
'expires_at' => (int) $story->expires_at->format('U'), 'type' => $s->type,
'seen' => $story->seen() 'preview_url' => url(Storage::url($s->path))
],
'url' => $url,
'seen' => StoryService::hasSeen($pid, StoryService::latest($s->profile_id)),
'sid' => $s->id
]; ];
})
->sortBy('seen')
->values();
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
} }
public function apiV1Profile(Request $request, $id) public function profile(Request $request, $id)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$authed = $request->user()->profile; $authed = $request->user()->profile_id;
$profile = Profile::findOrFail($id); $profile = Profile::findOrFail($id);
if($id == $authed->id) {
$publicOnly = true; if($authed != $profile->id && !FollowerService::follows($authed, $profile->id)) {
} else { return [];
$publicOnly = (bool) $profile->followedBy($authed);
} }
$stories = Story::whereProfileId($profile->id) $stories = Story::whereProfileId($profile->id)
->whereActive(true)
->orderBy('expires_at') ->orderBy('expires_at')
->where('expires_at', '>', now())
->when(!$publicOnly, function($query, $publicOnly) {
return $query->wherePublic(true);
})
->get() ->get()
->map(function($s, $k) { ->map(function($s, $k) use($authed) {
return [ $seen = StoryService::hasSeen($authed, $s->id);
'id' => $s->id, $res = [
'type' => Str::endsWith($s->path, '.mp4') ? 'video' :'photo', 'id' => (string) $s->id,
'length' => 3, 'type' => $s->type,
'duration' => $s->duration,
'src' => url(Storage::url($s->path)), 'src' => url(Storage::url($s->path)),
'preview' => null, 'created_at' => $s->created_at->toAtomString(),
'link' => null, 'expires_at' => $s->expires_at->toAtomString(),
'linkText' => null, 'view_count' => ($authed == $s->profile_id) ? ($s->view_count ?? 0) : null,
'time' => $s->created_at->format('U'), 'seen' => $seen,
'expires_at' => (int) $s->expires_at->format('U'), 'progress' => $seen ? 100 : 0,
'seen' => $s->seen() 'can_reply' => (bool) $s->can_reply,
'can_react' => (bool) $s->can_react
]; ];
if($s->type == 'poll') {
$res['question'] = json_decode($s->story, true)['question'];
$res['options'] = json_decode($s->story, true)['options'];
$res['voted'] = PollService::votedStory($s->id, $authed);
if($res['voted']) {
$res['voted_index'] = PollService::storyChoice($s->id, $authed);
}
}
return $res;
})->toArray(); })->toArray();
if(count($stories) == 0) { if(count($stories) == 0) {
return []; return [];
@ -253,110 +133,159 @@ class StoryController extends Controller
$cursor = count($stories) - 1; $cursor = count($stories) - 1;
$stories = [[ $stories = [[
'id' => (string) $stories[$cursor]['id'], 'id' => (string) $stories[$cursor]['id'],
'photo' => $profile->avatarUrl(), 'nodes' => $stories,
'name' => $profile->username, 'account' => AccountService::get($profile->id),
'link' => $profile->url(),
'lastUpdated' => (int) now()->format('U'),
'seen' => null,
'items' => $stories,
'pid' => (string) $profile->id 'pid' => (string) $profile->id
]]; ]];
return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
} }
public function apiV1Viewed(Request $request) public function viewed(Request $request)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [ $this->validate($request, [
'id' => 'required|integer|min:1|exists:stories', 'id' => 'required|min:1',
]); ]);
$id = $request->input('id'); $id = $request->input('id');
$authed = $request->user()->profile; $authed = $request->user()->profile;
$story = Story::with('profile') $story = Story::with('profile')
->where('expires_at', '>', now())
->orderByDesc('expires_at')
->findOrFail($id); ->findOrFail($id);
$exp = $story->expires_at;
$profile = $story->profile; $profile = $story->profile;
if($story->profile_id == $authed->id) { if($story->profile_id == $authed->id) {
$publicOnly = true; return [];
} else {
$publicOnly = (bool) $profile->followedBy($authed);
} }
$publicOnly = (bool) $profile->followedBy($authed);
abort_if(!$publicOnly, 403); abort_if(!$publicOnly, 403);
StoryView::firstOrCreate([
$v = StoryView::firstOrCreate([
'story_id' => $id, 'story_id' => $id,
'profile_id' => $authed->id 'profile_id' => $authed->id
]); ]);
if($v->wasRecentlyCreated) {
Story::findOrFail($story->id)->increment('view_count');
if($story->local == false) {
StoryViewDeliver::dispatch($story, $authed)->onQueue('story');
}
}
Cache::forget('stories:recent:by_id:' . $authed->id);
StoryService::addSeen($authed->id, $story->id);
return ['code' => 200]; return ['code' => 200];
} }
public function apiV1Exists(Request $request, $id) public function exists(Request $request, $id)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$res = (bool) Story::whereProfileId($id) return response()->json(Story::whereProfileId($id)
->where('expires_at', '>', now()) ->whereActive(true)
->count(); ->exists());
return response()->json($res);
}
public function apiV1Me(Request $request)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$profile = $request->user()->profile;
$stories = Story::whereProfileId($profile->id)
->orderBy('expires_at')
->where('expires_at', '>', now())
->get()
->map(function($s, $k) {
return [
'id' => $s->id,
'type' => Str::endsWith($s->path, '.mp4') ? 'video' :'photo',
'length' => 3,
'src' => url(Storage::url($s->path)),
'preview' => null,
'link' => null,
'linkText' => null,
'time' => $s->created_at->format('U'),
'expires_at' => (int) $s->expires_at->format('U'),
'seen' => true
];
})->toArray();
$ts = count($stories) ? last($stories)['time'] : null;
$res = [
'id' => (string) $profile->id,
'photo' => $profile->avatarUrl(),
'name' => $profile->username,
'link' => $profile->url(),
'lastUpdated' => $ts,
'seen' => true,
'items' => $stories
];
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
public function compose(Request $request)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
return view('stories.compose');
} }
public function iRedirect(Request $request) public function iRedirect(Request $request)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$user = $request->user(); $user = $request->user();
abort_if(!$user, 404); abort_if(!$user, 404);
$username = $user->username; $username = $user->username;
return redirect("/stories/{$username}"); return redirect("/stories/{$username}");
} }
public function viewers(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'sid' => 'required|string'
]);
$pid = $request->user()->profile_id;
$sid = $request->input('sid');
$story = Story::whereProfileId($pid)
->whereActive(true)
->findOrFail($sid);
$viewers = StoryView::whereStoryId($story->id)
->latest()
->simplePaginate(10)
->map(function($view) {
return AccountService::get($view->profile_id);
})
->values();
return response()->json($viewers, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
public function remoteStory(Request $request, $id)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$profile = Profile::findOrFail($id);
if($profile->user_id != null || $profile->domain == null) {
return redirect('/stories/' . $profile->username);
}
$pid = $profile->id;
return view('stories.show_remote', compact('pid'));
}
public function pollResults(Request $request)
{
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'sid' => 'required|string'
]);
$pid = $request->user()->profile_id;
$sid = $request->input('sid');
$story = Story::whereProfileId($pid)
->whereActive(true)
->findOrFail($sid);
return PollService::storyResults($sid);
}
public function getActivityObject(Request $request, $username, $id)
{
abort_if(!config_cache('instance.stories.enabled'), 404);
if(!$request->wantsJson()) {
return redirect('/stories/' . $username);
}
abort_if(!$request->hasHeader('Authorization'), 404);
$profile = Profile::whereUsername($username)->whereNull('domain')->firstOrFail();
$story = Story::whereActive(true)->whereProfileId($profile->id)->findOrFail($id);
abort_if($story->bearcap_token == null, 404);
abort_if(now()->gt($story->expires_at), 404);
$token = substr($request->header('Authorization'), 7);
abort_if(hash_equals($story->bearcap_token, $token) === false, 404);
abort_if($story->created_at->lt(now()->subMinutes(20)), 404);
$fractal = new Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Item($story, new StoryVerb());
$res = $fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
public function showSystemStory()
{
// return view('stories.system');
}
} }

View file

@ -2,12 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Auth, Cache;
use App\Follower;
use App\Profile;
use App\Status;
use App\User;
use App\UserFilter;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class TimelineController extends Controller class TimelineController extends Controller
@ -29,6 +23,7 @@ class TimelineController extends Controller
public function network(Request $request) public function network(Request $request)
{ {
abort_if(config('federation.network_timeline') == false, 404);
$this->validate($request, [ $this->validate($request, [
'layout' => 'nullable|string|in:grid,feed' 'layout' => 'nullable|string|in:grid,feed'
]); ]);

View file

@ -9,19 +9,16 @@ use Illuminate\Support\Str;
class UserInviteController extends Controller class UserInviteController extends Controller
{ {
public function __construct()
{
abort_if(!config('pixelfed.user_invites.enabled'), 404);
}
public function create(Request $request) public function create(Request $request)
{ {
abort_if(!config('pixelfed.user_invites.enabled'), 404);
abort_unless(Auth::check(), 403); abort_unless(Auth::check(), 403);
return view('settings.invites.create'); return view('settings.invites.create');
} }
public function show(Request $request) public function show(Request $request)
{ {
abort_if(!config('pixelfed.user_invites.enabled'), 404);
abort_unless(Auth::check(), 403); abort_unless(Auth::check(), 403);
$invites = UserInvite::whereUserId(Auth::id())->paginate(10); $invites = UserInvite::whereUserId(Auth::id())->paginate(10);
$limit = config('pixelfed.user_invites.limit.total'); $limit = config('pixelfed.user_invites.limit.total');
@ -31,6 +28,7 @@ class UserInviteController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
abort_if(!config('pixelfed.user_invites.enabled'), 404);
abort_unless(Auth::check(), 403); abort_unless(Auth::check(), 403);
$this->validate($request, [ $this->validate($request, [
'email' => 'required|email|unique:users|unique:user_invites', 'email' => 'required|email|unique:users|unique:user_invites',

View file

@ -32,10 +32,11 @@ class Kernel extends HttpKernel
\App\Http\Middleware\FrameGuard::class, \App\Http\Middleware\FrameGuard::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
// 'restricted', // 'restricted',
], ],

View file

@ -17,12 +17,13 @@ class EmailVerificationCheck
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if ($request->user() && if ($request->user() &&
config('pixelfed.enforce_email_verification') && config_cache('pixelfed.enforce_email_verification') &&
is_null($request->user()->email_verified_at) && is_null($request->user()->email_verified_at) &&
!$request->is( !$request->is(
'i/auth/*', 'i/auth/*',
'i/verify-email', 'i/verify-email*',
'log*', 'log*',
'site*',
'i/confirm-email/*', 'i/confirm-email/*',
'settings/home', 'settings/home',
'settings/email' 'settings/email'

View file

@ -54,11 +54,10 @@ class AvatarOptimize implements ShouldQueue
$img->fit(200, 200, function ($constraint) { $img->fit(200, 200, function ($constraint) {
$constraint->upsize(); $constraint->upsize();
}); });
$quality = config('pixelfed.image_quality'); $quality = config_cache('pixelfed.image_quality');
$img->save($file, $quality); $img->save($file, $quality);
$avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail(); $avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail();
$avatar->thumb_path = $avatar->media_path;
$avatar->change_count = ++$avatar->change_count; $avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = Carbon::now(); $avatar->last_processed_at = Carbon::now();
$avatar->save(); $avatar->save();

View file

@ -45,7 +45,6 @@ class CreateAvatar implements ShouldQueue
$avatar = new Avatar(); $avatar = new Avatar();
$avatar->profile_id = $profile->id; $avatar->profile_id = $profile->id;
$avatar->media_path = $path; $avatar->media_path = $path;
$avatar->thumb_path = $path;
$avatar->change_count = 0; $avatar->change_count = 0;
$avatar->last_processed_at = \Carbon\Carbon::now(); $avatar->last_processed_at = \Carbon\Carbon::now();
$avatar->save(); $avatar->save();

View file

@ -0,0 +1,113 @@
<?php
namespace App\Jobs\AvatarPipeline;
use App\Avatar;
use App\Profile;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Util\ActivityPub\Helpers;
use Illuminate\Support\Str;
use Zttp\Zttp;
use App\Http\Controllers\AvatarController;
use Storage;
use Log;
use Illuminate\Http\File;
use App\Services\MediaStorageService;
use App\Services\ActivityPubFetchService;
class RemoteAvatarFetch implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $profile;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Profile $profile)
{
$this->profile = $profile;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$profile = $this->profile;
if(config_cache('pixelfed.cloud_storage') == false && config_cache('federation.avatars.store_local') == false) {
return 1;
}
if($profile->domain == null || $profile->private_key) {
return 1;
}
$avatar = Avatar::whereProfileId($profile->id)->first();
if(!$avatar) {
$avatar = new Avatar;
$avatar->profile_id = $profile->id;
$avatar->save();
}
if($avatar->media_path == null && $avatar->remote_url == null) {
$avatar->media_path = 'public/avatars/default.jpg';
$avatar->is_remote = true;
$avatar->save();
}
$person = Helpers::fetchFromUrl($profile->remote_url);
if(!$person || !isset($person['@context'])) {
return 1;
}
if( !isset($person['icon']) ||
!isset($person['icon']['type']) ||
!isset($person['icon']['url'])
) {
return 1;
}
if($person['icon']['type'] !== 'Image') {
return 1;
}
if(!Helpers::validateUrl($person['icon']['url'])) {
return 1;
}
$icon = $person['icon'];
$avatar->remote_url = $icon['url'];
$avatar->save();
MediaStorageService::avatar($avatar, config_cache('pixelfed.cloud_storage') == false);
return 1;
}
}

View file

@ -8,6 +8,7 @@ use App\{
UserFilter UserFilter
}; };
use App\Services\NotificationService; use App\Services\NotificationService;
use App\Services\StatusService;
use DB, Cache, Log; use DB, Cache, Log;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Redis;
@ -58,6 +59,11 @@ class CommentPipeline implements ShouldQueue
$target = $status->profile; $target = $status->profile;
$actor = $comment->profile; $actor = $comment->profile;
DB::transaction(function() use($status) {
$status->reply_count = DB::table('statuses')->whereInReplyToId($status->id)->count();
$status->save();
});
if ($actor->id === $target->id || $status->comments_disabled == true) { if ($actor->id === $target->id || $status->comments_disabled == true) {
return true; return true;
} }
@ -85,6 +91,16 @@ class CommentPipeline implements ShouldQueue
NotificationService::setNotification($notification); NotificationService::setNotification($notification);
NotificationService::set($notification->profile_id, $notification->id); NotificationService::set($notification->profile_id, $notification->id);
StatusService::del($comment->id);
}); });
if($exists = Cache::get('status:replies:all:' . $status->id)) {
if($exists && $exists->count() == 3) {
} else {
Cache::forget('status:replies:all:' . $status->id);
}
} else {
Cache::forget('status:replies:all:' . $status->id);
}
} }
} }

View file

@ -0,0 +1,93 @@
<?php
namespace App\Jobs\DeletePipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Cache;
use DB;
use Illuminate\Support\Str;
use App\Profile;
use App\Util\ActivityPub\Helpers;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use App\Util\ActivityPub\HttpSignature;
class FanoutDeletePipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $profile;
public $timeout = 300;
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($profile)
{
$this->profile = $profile;
}
public function handle()
{
$profile = $this->profile;
$client = new Client([
'timeout' => config('federation.activitypub.delivery.timeout')
]);
$audience = Cache::remember('pf:ap:known_instances', now()->addHours(6), function() {
return Profile::whereNotNull('sharedInbox')->groupBy('sharedInbox')->pluck('sharedInbox')->toArray();
});
$activity = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => $profile->permalink('#delete'),
"type" => "Delete",
"actor" => $profile->permalink(),
"to" => [
"https://www.w3.org/ns/activitystreams#Public",
],
"object" => $profile->permalink(),
];
$payload = json_encode($activity);
$requests = function($audience) use ($client, $activity, $profile, $payload) {
foreach($audience as $url) {
$headers = HttpSignature::sign($profile, $url, $activity);
yield function() use ($client, $url, $headers, $payload) {
return $client->postAsync($url, [
'curl' => [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HEADER => true
]
]);
};
}
};
$pool = new Pool($client, $requests($audience), [
'concurrency' => config('federation.activitypub.delivery.concurrency'),
'fulfilled' => function ($response, $index) {
},
'rejected' => function ($reason, $index) {
}
]);
$promise = $pool->promise();
$promise->wait();
return 1;
}
}

View file

@ -46,6 +46,9 @@ class FollowPipeline implements ShouldQueue
$actor = $follower->actor; $actor = $follower->actor;
$target = $follower->target; $target = $follower->target;
Cache::forget('profile:following:' . $actor->id);
Cache::forget('profile:following:' . $target->id);
if($target->domain || !$target->private_key) { if($target->domain || !$target->private_key) {
return; return;
} }

View file

@ -41,7 +41,7 @@ class ImageOptimize implements ShouldQueue
{ {
$media = $this->media; $media = $this->media;
$path = storage_path('app/'.$media->media_path); $path = storage_path('app/'.$media->media_path);
if (!is_file($path)) { if (!is_file($path) || $media->skip_optimize) {
return; return;
} }

View file

@ -45,7 +45,7 @@ class ImageResize implements ShouldQueue
return; return;
} }
$path = storage_path('app/'.$media->media_path); $path = storage_path('app/'.$media->media_path);
if (!is_file($path)) { if (!is_file($path) || $media->skip_optimize) {
return; return;
} }

View file

@ -11,6 +11,8 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use ImageOptimizer; use ImageOptimizer;
use Illuminate\Http\File; use Illuminate\Http\File;
use App\Services\MediaPathService;
use App\Jobs\MediaPipeline\MediaStoragePipeline;
class ImageUpdate implements ShouldQueue class ImageUpdate implements ShouldQueue
{ {
@ -21,6 +23,7 @@ class ImageUpdate implements ShouldQueue
protected $protectedMimes = [ protected $protectedMimes = [
'image/jpeg', 'image/jpeg',
'image/png', 'image/png',
'image/webp'
]; ];
/** /**
@ -60,8 +63,10 @@ class ImageUpdate implements ShouldQueue
if (in_array($media->mime, $this->protectedMimes) == true) { if (in_array($media->mime, $this->protectedMimes) == true) {
ImageOptimizer::optimize($thumb); ImageOptimizer::optimize($thumb);
if(!$media->skip_optimize) {
ImageOptimizer::optimize($path); ImageOptimizer::optimize($path);
} }
}
if (!is_file($path) || !is_file($thumb)) { if (!is_file($path) || !is_file($thumb)) {
return; return;
@ -73,19 +78,6 @@ class ImageUpdate implements ShouldQueue
$media->size = $total; $media->size = $total;
$media->save(); $media->save();
if(config('pixelfed.cloud_storage') == true) { MediaStoragePipeline::dispatch($media);
$p = explode('/', $media->media_path);
$monthHash = $p[2];
$userHash = $p[3];
$storagePath = "public/m/{$monthHash}/{$userHash}";
$file = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($path), 'public');
$url = Storage::disk(config('filesystems.cloud'))->url($file);
$thumbFile = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($thumb), 'public');
$thumbUrl = Storage::disk(config('filesystems.cloud'))->url($thumbFile);
$media->thumbnail_url = $thumbUrl;
$media->cdn_url = $url;
$media->optimized_url = $url;
$media->save();
}
} }
} }

View file

@ -49,7 +49,7 @@ class ImportInstagram implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
if(config('pixelfed.import.instagram.enabled') != true) { if(config_cache('pixelfed.import.instagram.enabled') != true) {
return; return;
} }
@ -101,7 +101,11 @@ class ImportInstagram implements ShouldQueue
$path = storage_path("app/$importData->path"); $path = storage_path("app/$importData->path");
$storagePath = "public/m/{$monthHash}/{$userHash}"; $storagePath = "public/m/{$monthHash}/{$userHash}";
$newPath = "app/$storagePath/$filename"; $dir = "app/$storagePath";
if(!is_dir(storage_path($dir))) {
mkdir(storage_path($dir), 0755, true);
}
$newPath = "$dir/$filename";
$fs->move($path,storage_path($newPath)); $fs->move($path,storage_path($newPath));
$path = $newPath; $path = $newPath;
$hash = \hash_file('sha256', storage_path($path)); $hash = \hash_file('sha256', storage_path($path));

View file

@ -0,0 +1,223 @@
<?php
namespace App\Jobs\InboxPipeline;
use Cache;
use App\Profile;
use App\Util\ActivityPub\{
Helpers,
HttpSignature,
Inbox
};
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Zttp\Zttp;
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
class DeleteWorker implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $headers;
protected $payload;
public $timeout = 60;
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($headers, $payload)
{
$this->headers = $headers;
$this->payload = $payload;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$profile = null;
$headers = $this->headers;
$payload = json_decode($this->payload, true, 8);
if(isset($payload['id'])) {
$lockKey = 'pf:ap:del-lock:' . hash('sha256', $payload['id']);
if(Cache::get($lockKey) !== null) {
// Job processed already
return 1;
}
Cache::put($lockKey, 1, 300);
}
if(!isset($headers['signature']) || !isset($headers['date'])) {
return;
}
if(empty($headers) || empty($payload)) {
return;
}
if( $payload['type'] === 'Delete' &&
( ( is_string($payload['object']) &&
$payload['object'] === $payload['actor'] ) ||
( is_array($payload['object']) &&
isset($payload['object']['id'], $payload['object']['type']) &&
$payload['object']['type'] === 'Person' &&
$payload['actor'] === $payload['object']['id']
))
) {
$actor = $payload['actor'];
$hash = strlen($actor) <= 48 ?
'b:' . base64_encode($actor) :
'h:' . hash('sha256', $actor);
$lockKey = 'ap:inbox:actor-delete-exists:lock:' . $hash;
Cache::lock($lockKey, 10)->block(5, function () use(
$headers,
$payload,
$actor,
$hash
) {
$key = 'ap:inbox:actor-delete-exists:' . $hash;
$actorDelete = Cache::remember($key, now()->addMinutes(15), function() use($actor) {
return Profile::whereRemoteUrl($actor)
->whereNotNull('domain')
->exists();
});
if($actorDelete) {
if($this->verifySignature($headers, $payload) == true) {
Cache::set($key, false);
$profile = Profile::whereNotNull('domain')
->whereNull('status')
->whereRemoteUrl($actor)
->first();
if($profile) {
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('delete');
}
return;
} else {
// Signature verification failed, exit.
return;
}
} else {
// Remote user doesn't exist, exit early.
return;
}
});
return;
}
$profile = null;
if($this->verifySignature($headers, $payload) == true) {
(new Inbox($headers, $profile, $payload))->handle();
return;
} else if($this->blindKeyRotation($headers, $payload) == true) {
(new Inbox($headers, $profile, $payload))->handle();
return;
} else {
return;
}
}
protected function verifySignature($headers, $payload)
{
$body = $this->payload;
$bodyDecoded = $payload;
$signature = is_array($headers['signature']) ? $headers['signature'][0] : $headers['signature'];
$date = is_array($headers['date']) ? $headers['date'][0] : $headers['date'];
if(!$signature) {
return;
}
if(!$date) {
return;
}
if(!now()->parse($date)->gt(now()->subDays(1)) ||
!now()->parse($date)->lt(now()->addDays(1))
) {
return;
}
$signatureData = HttpSignature::parseSignatureHeader($signature);
$keyId = Helpers::validateUrl($signatureData['keyId']);
$id = Helpers::validateUrl($bodyDecoded['id']);
$keyDomain = parse_url($keyId, PHP_URL_HOST);
$idDomain = parse_url($id, PHP_URL_HOST);
if(isset($bodyDecoded['object'])
&& is_array($bodyDecoded['object'])
&& isset($bodyDecoded['object']['attributedTo'])
) {
if(parse_url($bodyDecoded['object']['attributedTo'], PHP_URL_HOST) !== $keyDomain) {
return;
}
}
if(!$keyDomain || !$idDomain || $keyDomain !== $idDomain) {
return;
}
$actor = Profile::whereKeyId($keyId)->first();
if(!$actor) {
$actorUrl = is_array($bodyDecoded['actor']) ? $bodyDecoded['actor'][0] : $bodyDecoded['actor'];
$actor = Helpers::profileFirstOrNew($actorUrl);
}
if(!$actor) {
return;
}
$pkey = openssl_pkey_get_public($actor->public_key);
if(!$pkey) {
return 0;
}
$inboxPath = "/f/inbox";
list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $headers, $inboxPath, $body);
if($verified == 1) {
return true;
} else {
return false;
}
}
protected function blindKeyRotation($headers, $payload)
{
$signature = is_array($headers['signature']) ? $headers['signature'][0] : $headers['signature'];
$date = is_array($headers['date']) ? $headers['date'][0] : $headers['date'];
if(!$signature) {
return;
}
if(!$date) {
return;
}
if(!now()->parse($date)->gt(now()->subDays(1)) ||
!now()->parse($date)->lt(now()->addDays(1))
) {
return;
}
$signatureData = HttpSignature::parseSignatureHeader($signature);
$keyId = Helpers::validateUrl($signatureData['keyId']);
$actor = Profile::whereKeyId($keyId)->whereNotNull('remote_url')->first();
if(!$actor) {
return;
}
if(Helpers::validateUrl($actor->remote_url) == false) {
return;
}
$res = Zttp::timeout(5)->withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
])->get($actor->remote_url);
$res = json_decode($res->body(), true, 8);
if($res['publicKey']['id'] !== $actor->key_id) {
return;
}
$actor->public_key = $res['publicKey']['publicKeyPem'];
$actor->save();
return $this->verifySignature($headers, $payload);
}
}

View file

@ -53,6 +53,15 @@ class InboxValidator implements ShouldQueue
$profile = Profile::whereNull('domain')->whereUsername($username)->first(); $profile = Profile::whereNull('domain')->whereUsername($username)->first();
if(isset($payload['id'])) {
$lockKey = hash('sha256', $payload['id']);
if(Cache::get($lockKey) !== null) {
// Job processed already
return 1;
}
Cache::put($lockKey, 1, 300);
}
if(!isset($headers['signature']) || !isset($headers['date'])) { if(!isset($headers['signature']) || !isset($headers['date'])) {
return; return;
} }
@ -173,6 +182,9 @@ class InboxValidator implements ShouldQueue
return; return;
} }
$pkey = openssl_pkey_get_public($actor->public_key); $pkey = openssl_pkey_get_public($actor->public_key);
if(!$pkey) {
return 0;
}
$inboxPath = "/users/{$profile->username}/inbox"; $inboxPath = "/users/{$profile->username}/inbox";
list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $headers, $inboxPath, $body); list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $headers, $inboxPath, $body);
if($verified == 1) { if($verified == 1) {

View file

@ -49,6 +49,15 @@ class InboxWorker implements ShouldQueue
$headers = $this->headers; $headers = $this->headers;
$payload = json_decode($this->payload, true, 8); $payload = json_decode($this->payload, true, 8);
if(isset($payload['id'])) {
$lockKey = hash('sha256', $payload['id']);
if(Cache::get($lockKey) !== null) {
// Job processed already
return 1;
}
Cache::put($lockKey, 1, 300);
}
if(!isset($headers['signature']) || !isset($headers['date'])) { if(!isset($headers['signature']) || !isset($headers['date'])) {
return; return;
} }
@ -161,6 +170,9 @@ class InboxWorker implements ShouldQueue
return; return;
} }
$pkey = openssl_pkey_get_public($actor->public_key); $pkey = openssl_pkey_get_public($actor->public_key);
if(!$pkey) {
return 0;
}
$inboxPath = "/f/inbox"; $inboxPath = "/f/inbox";
list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $headers, $inboxPath, $body); list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $headers, $inboxPath, $body);
if($verified == 1) { if($verified == 1) {

View file

@ -0,0 +1,56 @@
<?php
namespace App\Jobs\InstancePipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use App\Instance;
use App\Profile;
use App\Services\NodeinfoService;
class FetchNodeinfoPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $instance;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Instance $instance)
{
$this->instance = $instance;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$instance = $this->instance;
$ni = NodeinfoService::get($instance->domain);
if($ni) {
if(isset($ni['software']) && is_array($ni['software']) && isset($ni['software']['name'])) {
$software = $ni['software']['name'];
$instance->software = strtolower(strip_tags($software));
$instance->last_crawled_at = now();
$instance->user_count = Profile::whereDomain($instance->domain)->count();
$instance->save();
}
} else {
$instance->user_count = Profile::whereDomain($instance->domain)->count();
$instance->last_crawled_at = now();
$instance->save();
}
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace App\Jobs\InstancePipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use App\Instance;
use App\Profile;
use App\Services\NodeinfoService;
class InstanceCrawlPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Instance::whereNull('last_crawled_at')->whereNull('software')->chunk(50, function($instances) {
foreach($instances as $instance) {
FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
}
});
}
}

View file

@ -2,7 +2,7 @@
namespace App\Jobs\LikePipeline; namespace App\Jobs\LikePipeline;
use Cache, Log; use Cache, DB, Log;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Redis;
use App\{Like, Notification}; use App\{Like, Notification};
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
@ -14,6 +14,7 @@ use App\Util\ActivityPub\Helpers;
use League\Fractal; use League\Fractal;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\Like as LikeTransformer; use App\Transformer\ActivityPub\Verb\Like as LikeTransformer;
use App\Services\StatusService;
class LikePipeline implements ShouldQueue class LikePipeline implements ShouldQueue
{ {
@ -58,6 +59,11 @@ class LikePipeline implements ShouldQueue
return; return;
} }
$status->likes_count = DB::table('likes')->whereStatusId($status->id)->count();
$status->save();
StatusService::refresh($status->id);
if($status->url && $actor->domain == null) { if($status->url && $actor->domain == null) {
return $this->remoteLikeDeliver(); return $this->remoteLikeDeliver();
} }

View file

@ -0,0 +1,108 @@
<?php
namespace App\Jobs\LikePipeline;
use Cache, DB, Log;
use Illuminate\Support\Facades\Redis;
use App\{Like, Notification};
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Util\ActivityPub\Helpers;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\UndoLike as LikeTransformer;
use App\Services\StatusService;
class UnlikePipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $like;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
public $timeout = 5;
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Like $like)
{
$this->like = $like;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$like = $this->like;
$status = $this->like->status;
$actor = $this->like->actor;
if (!$status) {
// Ignore notifications to deleted statuses
return;
}
$status->likes_count = DB::table('likes')->whereStatusId($status->id)->count();
$status->save();
StatusService::refresh($status->id);
if($actor->id !== $status->profile_id && $status->url && $actor->domain == null) {
$this->remoteLikeDeliver();
}
$exists = Notification::whereProfileId($status->profile_id)
->whereActorId($actor->id)
->whereAction('like')
->whereItemId($status->id)
->whereItemType('App\Status')
->first();
if($exists) {
$exists->delete();
}
$like = Like::whereProfileId($actor->id)->whereStatusId($status->id)->first();
if(!$like) {
return;
}
$like->forceDelete();
return;
}
public function remoteLikeDeliver()
{
$like = $this->like;
$status = $this->like->status;
$actor = $this->like->actor;
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($like, new LikeTransformer());
$activity = $fractal->createData($resource)->toArray();
$url = $status->profile->sharedInbox ?? $status->profile->inbox_url;
Helpers::sendSignedObject($actor, $url, $activity);
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace App\Jobs\MediaPipeline;
use App\Media;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
class MediaDeletePipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $media;
public function __construct(Media $media)
{
$this->media = $media;
}
public function handle()
{
$media = $this->media;
$path = $media->media_path;
$thumb = $media->thumbnail_path;
if(!$path) {
return 1;
}
$e = explode('/', $path);
array_pop($e);
$i = implode('/', $e);
if(config_cache('pixelfed.cloud_storage') == true) {
$disk = Storage::disk(config('filesystems.cloud'));
if($disk->exists($path)) {
$disk->delete($path);
}
if($disk->exists($thumb)) {
$disk->delete($thumb);
}
if(count($e) > 4 && count($disk->files($i)) == 0) {
$disk->deleteDirectory($i);
}
}
$disk = Storage::disk(config('filesystems.local'));
if($disk->exists($path)) {
$disk->delete($path);
}
if($disk->exists($thumb)) {
$disk->delete($thumb);
}
if(count($e) > 4 && count($disk->files($i)) == 0) {
$disk->deleteDirectory($i);
}
return 1;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Jobs\MediaPipeline;
use App\Media;
use Cache;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Redis;
use App\Services\MediaStorageService;
class MediaStoragePipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $media;
public function __construct(Media $media)
{
$this->media = $media;
}
public function handle()
{
MediaStorageService::store($this->media);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Jobs\MediaPipeline;
use App\Media;
use App\User;
use Cache;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Services\StatusService;
class MediaSyncLicensePipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $userId;
protected $licenseId;
public function __construct($userId, $licenseId)
{
$this->userId = $userId;
$this->licenseId = $licenseId;
}
public function handle()
{
$licenseId = $this->licenseId;
if(!$licenseId || !$this->userId) {
return 1;
}
Media::whereUserId($this->userId)
->chunk(100, function($medias) use($licenseId) {
foreach($medias as $media) {
$media->license = $licenseId;
$media->save();
Cache::forget('status:transformer:media:attachments:'. $media->status_id);
StatusService::del($media->status_id);
}
});
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace App\Jobs\ModPipeline;
use Cache;
use App\Profile;
use App\Status;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Services\StatusService;
class HandleSpammerPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $profile;
public $deleteWhenMissingModels = true;
public function __construct(Profile $profile)
{
$this->profile = $profile;
}
public function handle()
{
$profile = $this->profile;
$profile->unlisted = true;
$profile->cw = true;
$profile->no_autolink = true;
$profile->save();
Status::whereProfileId($profile->id)
->chunk(50, function($statuses) {
foreach($statuses as $status) {
$status->is_nsfw = true;
$status->scope = $status->scope === 'public' ? 'unlisted' : $status->scope;
$status->visibility = $status->scope;
$status->save();
StatusService::del($status->id, true);
}
});
Cache::forget('_api:statuses:recent_9:'.$profile->id);
return 1;
}
}

View file

@ -15,6 +15,7 @@ use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\Announce; use App\Transformer\ActivityPub\Verb\Announce;
use GuzzleHttp\{Pool, Client, Promise}; use GuzzleHttp\{Pool, Client, Promise};
use App\Util\ActivityPub\HttpSignature; use App\Util\ActivityPub\HttpSignature;
use App\Services\StatusService;
class SharePipeline implements ShouldQueue class SharePipeline implements ShouldQueue
{ {
@ -47,8 +48,9 @@ class SharePipeline implements ShouldQueue
public function handle() public function handle()
{ {
$status = $this->status; $status = $this->status;
$parent = $this->status->parent();
$actor = $status->profile; $actor = $status->profile;
$target = $status->parent()->profile; $target = $parent->profile;
if ($status->uri !== null) { if ($status->uri !== null) {
// Ignore notifications to remote statuses // Ignore notifications to remote statuses
@ -60,19 +62,23 @@ class SharePipeline implements ShouldQueue
->whereAction('share') ->whereAction('share')
->whereItemId($status->reblog_of_id) ->whereItemId($status->reblog_of_id)
->whereItemType('App\Status') ->whereItemType('App\Status')
->count(); ->exists();
if($target->id === $status->profile_id) { if($target->id === $status->profile_id) {
$this->remoteAnnounceDeliver(); $this->remoteAnnounceDeliver();
return true; return true;
} }
if( $exists !== 0) { if($exists === true) {
return true; return true;
} }
$this->remoteAnnounceDeliver(); $this->remoteAnnounceDeliver();
$parent->reblogs_count = $parent->shares()->count();
$parent->save();
StatusService::del($parent->id);
try { try {
$notification = new Notification; $notification = new Notification;
$notification->profile_id = $target->id; $notification->profile_id = $target->id;
@ -94,7 +100,7 @@ class SharePipeline implements ShouldQueue
public function remoteAnnounceDeliver() public function remoteAnnounceDeliver()
{ {
if(config('federation.activitypub.enabled') == false) { if(config_cache('federation.activitypub.enabled') == false) {
return true; return true;
} }
$status = $this->status; $status = $this->status;

View file

@ -0,0 +1,118 @@
<?php
namespace App\Jobs\SharePipeline;
use Cache, Log;
use Illuminate\Support\Facades\Redis;
use App\{Status, Notification};
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\UndoAnnounce;
use GuzzleHttp\{Pool, Client, Promise};
use App\Util\ActivityPub\HttpSignature;
use App\Services\StatusService;
class UndoSharePipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $status;
public $deleteWhenMissingModels = true;
public function __construct(Status $status)
{
$this->status = $status;
}
public function handle()
{
$status = $this->status;
$actor = $status->profile;
$parent = $status->parent();
$target = $status->parent()->profile;
if ($status->uri !== null) {
return;
}
if($target->domain === null) {
Notification::whereProfileId($target->id)
->whereActorId($status->profile_id)
->whereAction('share')
->whereItemId($status->reblog_of_id)
->whereItemType('App\Status')
->delete();
}
$this->remoteAnnounceDeliver();
if($parent->reblogs_count > 0) {
$parent->reblogs_count = $parent->reblogs_count - 1;
$parent->save();
StatusService::del($parent->id);
}
$status->forceDelete();
return 1;
}
public function remoteAnnounceDeliver()
{
if(config_cache('federation.activitypub.enabled') == false) {
return 1;
}
$status = $this->status;
$profile = $status->profile;
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($status, new UndoAnnounce());
$activity = $fractal->createData($resource)->toArray();
$audience = $status->profile->getAudienceInbox();
if(empty($audience) || $status->scope != 'public') {
return 1;
}
$payload = json_encode($activity);
$client = new Client([
'timeout' => config('federation.activitypub.delivery.timeout')
]);
$requests = function($audience) use ($client, $activity, $profile, $payload) {
foreach($audience as $url) {
$headers = HttpSignature::sign($profile, $url, $activity);
yield function() use ($client, $url, $headers, $payload) {
return $client->postAsync($url, [
'curl' => [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HEADER => true
]
]);
};
}
};
$pool = new Pool($client, $requests($audience), [
'concurrency' => config('federation.activitypub.delivery.concurrency'),
'fulfilled' => function ($response, $index) {
},
'rejected' => function ($reason, $index) {
}
]);
$promise = $pool->promise();
$promise->wait();
}
}

View file

@ -12,6 +12,7 @@ use Illuminate\Queue\SerializesModels;
use League\Fractal; use League\Fractal;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\CreateNote; use App\Transformer\ActivityPub\Verb\CreateNote;
use App\Transformer\ActivityPub\Verb\CreateQuestion;
use App\Util\ActivityPub\Helpers; use App\Util\ActivityPub\Helpers;
use GuzzleHttp\Pool; use GuzzleHttp\Pool;
use GuzzleHttp\Client; use GuzzleHttp\Client;
@ -62,10 +63,20 @@ class StatusActivityPubDeliver implements ShouldQueue
return; return;
} }
switch($status->type) {
case 'poll':
$activitypubObject = new CreateQuestion();
break;
default:
$activitypubObject = new CreateNote();
break;
}
$fractal = new Fractal\Manager(); $fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer()); $fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($status, new CreateNote()); $resource = new Fractal\Resource\Item($status, $activitypubObject);
$activity = $fractal->createData($resource)->toArray(); $activity = $fractal->createData($resource)->toArray();
$payload = json_encode($activity); $payload = json_encode($activity);
@ -82,7 +93,9 @@ class StatusActivityPubDeliver implements ShouldQueue
'curl' => [ 'curl' => [
CURLOPT_HTTPHEADER => $headers, CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload, CURLOPT_POSTFIELDS => $payload,
CURLOPT_HEADER => true CURLOPT_HEADER => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
] ]
]); ]);
}; };

View file

@ -2,7 +2,7 @@
namespace App\Jobs\StatusPipeline; namespace App\Jobs\StatusPipeline;
use DB; use DB, Storage;
use App\{ use App\{
AccountInterstitial, AccountInterstitial,
MediaTag, MediaTag,
@ -17,6 +17,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use League\Fractal; use League\Fractal;
use Illuminate\Support\Str;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\DeleteNote; use App\Transformer\ActivityPub\Verb\DeleteNote;
use App\Util\ActivityPub\Helpers; use App\Util\ActivityPub\Helpers;
@ -24,6 +25,8 @@ use GuzzleHttp\Pool;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Promise; use GuzzleHttp\Promise;
use App\Util\ActivityPub\HttpSignature; use App\Util\ActivityPub\HttpSignature;
use App\Services\StatusService;
use App\Services\MediaStorageService;
class StatusDelete implements ShouldQueue class StatusDelete implements ShouldQueue
{ {
@ -58,17 +61,14 @@ class StatusDelete implements ShouldQueue
$status = $this->status; $status = $this->status;
$profile = $this->status->profile; $profile = $this->status->profile;
$count = $profile->statuses() StatusService::del($status->id, true);
->getQuery()
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->count();
$profile->status_count = ($count - 1); if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
$profile->status_count = $profile->status_count - 1;
$profile->save(); $profile->save();
}
if(config('federation.activitypub.enabled') == true) { if(config_cache('federation.activitypub.enabled') == true) {
$this->fanoutDelete($status); $this->fanoutDelete($status);
} else { } else {
$this->unlinkRemoveMedia($status); $this->unlinkRemoveMedia($status);
@ -79,20 +79,9 @@ class StatusDelete implements ShouldQueue
public function unlinkRemoveMedia($status) public function unlinkRemoveMedia($status)
{ {
foreach ($status->media as $media) { foreach ($status->media as $media) {
$thumbnail = storage_path("app/{$media->thumbnail_path}"); MediaStorageService::delete($media, true);
$photo = storage_path("app/{$media->media_path}"); }
try {
if (is_file($thumbnail)) {
unlink($thumbnail);
}
if (is_file($photo)) {
unlink($photo);
}
$media->delete();
} catch (Exception $e) {
}
}
if($status->in_reply_to_id) { if($status->in_reply_to_id) {
DB::transaction(function() use($status) { DB::transaction(function() use($status) {
$parent = Status::findOrFail($status->in_reply_to_id); $parent = Status::findOrFail($status->in_reply_to_id);
@ -117,7 +106,6 @@ class StatusDelete implements ShouldQueue
Report::whereObjectType('App\Status') Report::whereObjectType('App\Status')
->whereObjectId($status->id) ->whereObjectId($status->id)
->delete(); ->delete();
MediaTag::where('status_id', $status->id) MediaTag::where('status_id', $status->id)
->cursor() ->cursor()
->each(function($tag) { ->each(function($tag) {
@ -126,7 +114,6 @@ class StatusDelete implements ShouldQueue
->forceDelete(); ->forceDelete();
$tag->delete(); $tag->delete();
}); });
AccountInterstitial::where('item_type', 'App\Status') AccountInterstitial::where('item_type', 'App\Status')
->where('item_id', $status->id) ->where('item_id', $status->id)
->delete(); ->delete();

View file

@ -52,16 +52,12 @@ class StatusEntityLexer implements ShouldQueue
public function handle() public function handle()
{ {
$profile = $this->status->profile; $profile = $this->status->profile;
$status = $this->status;
$count = $profile->statuses() if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
->getQuery() $profile->status_count = $profile->status_count + 1;
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->count();
$profile->status_count = $count;
$profile->save(); $profile->save();
}
if($profile->no_autolink == false) { if($profile->no_autolink == false) {
$this->parseEntities(); $this->parseEntities();
@ -107,9 +103,13 @@ class StatusEntityLexer implements ShouldQueue
} }
DB::transaction(function () use ($status, $tag) { DB::transaction(function () use ($status, $tag) {
$slug = str_slug($tag, '-', false); $slug = str_slug($tag, '-', false);
$hashtag = Hashtag::firstOrCreate( $hashtag = Hashtag::where('slug', $slug)->first();
if (!$hashtag) {
$hashtag = Hashtag::create(
['name' => $tag, 'slug' => $slug] ['name' => $tag, 'slug' => $slug]
); );
}
StatusHashtag::firstOrCreate( StatusHashtag::firstOrCreate(
[ [
'status_id' => $status->id, 'status_id' => $status->id,
@ -150,17 +150,29 @@ class StatusEntityLexer implements ShouldQueue
public function deliver() public function deliver()
{ {
$status = $this->status; $status = $this->status;
$types = [
'photo',
'photo:album',
'video',
'video:album',
'photo:video:album'
];
if(config('pixelfed.bouncer.enabled')) { if(config_cache('pixelfed.bouncer.enabled')) {
Bouncer::get($status); Bouncer::get($status);
} }
if($status->uri == null && $status->scope == 'public') { if( $status->uri == null &&
$status->scope == 'public' &&
in_array($status->type, $types) &&
$status->in_reply_to_id === null &&
$status->reblog_of_id === null
) {
PublicTimelineService::add($status->id); PublicTimelineService::add($status->id);
} }
if(config('federation.activitypub.enabled') == true && config('app.env') == 'production') { if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') {
StatusActivityPubDeliver::dispatch($this->status); StatusActivityPubDeliver::dispatch($status);
} }
} }
} }

View file

@ -0,0 +1,89 @@
<?php
namespace App\Jobs\StatusPipeline;
use App\Notification;
use App\Status;
use Cache;
use DB;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Redis;
use App\Services\NotificationService;
class StatusReplyPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $status;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
public $timeout = 5;
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Status $status)
{
$this->status = $status;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$status = $this->status;
$actor = $status->profile;
$reply = Status::find($status->in_reply_to_id);
if(!$actor || !$reply) {
return 1;
}
$target = $reply->profile;
$exists = Notification::whereProfileId($target->id)
->whereActorId($actor->id)
->whereIn('action', ['mention', 'comment'])
->whereItemId($status->id)
->whereItemType('App\Status')
->count();
if ($actor->id === $target || $exists !== 0) {
return 1;
}
DB::transaction(function() use($target, $actor, $status) {
$notification = new Notification();
$notification->profile_id = $target->id;
$notification->actor_id = $actor->id;
$notification->action = 'comment';
$notification->message = $status->replyToText();
$notification->rendered = $status->replyToHtml();
$notification->item_id = $status->id;
$notification->item_type = "App\Status";
$notification->save();
NotificationService::setNotification($notification);
NotificationService::set($notification->profile_id, $notification->id);
});
return 1;
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Jobs\StatusPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Services\CustomEmojiService;
use App\Services\StatusService;
class StatusTagsPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $activity;
protected $status;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($activity, $status)
{
$this->activity = $activity;
$this->status = $status;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$res = $this->activity;
collect($res['tag'])
->filter(function($tag) {
// todo: finish hashtag + mention import
// return in_array($tag['type'], ['Emoji', 'Hashtag', 'Mention']);
return $tag && $tag['type'] == 'Emoji';
})
->map(function($tag) {
CustomEmojiService::import($tag['id'], $this->status->id);
});
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace App\Jobs\StoryPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Storage;
use App\Story;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\DeleteStory;
use App\Util\ActivityPub\Helpers;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use App\Util\ActivityPub\HttpSignature;
use App\Services\FollowerService;
use App\Services\StoryService;
class StoryDelete implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $story;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Story $story)
{
$this->story = $story;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$story = $this->story;
if($story->local == false) {
return;
}
StoryService::removeRotateQueue($story->id);
StoryService::delLatest($story->profile_id);
StoryService::delById($story->id);
if(Storage::exists($story->path) == true) {
Storage::delete($story->path);
}
$story->views()->delete();
$profile = $story->profile;
$activity = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $story->url() . '#delete',
'type' => 'Delete',
'actor' => $profile->permalink(),
'object' => [
'id' => $story->url(),
'type' => 'Story',
],
];
$this->fanoutExpiry($profile, $activity);
// delete notifications
// delete polls
// delete reports
$story->delete();
return;
}
protected function fanoutExpiry($profile, $activity)
{
$audience = FollowerService::softwareAudience($profile->id, 'pixelfed');
if(empty($audience)) {
// Return on profiles with no remote followers
return;
}
$payload = json_encode($activity);
$client = new Client([
'timeout' => config('federation.activitypub.delivery.timeout')
]);
$requests = function($audience) use ($client, $activity, $profile, $payload) {
foreach($audience as $url) {
$headers = HttpSignature::sign($profile, $url, $activity);
yield function() use ($client, $url, $headers, $payload) {
return $client->postAsync($url, [
'curl' => [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HEADER => true
]
]);
};
}
};
$pool = new Pool($client, $requests($audience), [
'concurrency' => config('federation.activitypub.delivery.concurrency'),
'fulfilled' => function ($response, $index) {
},
'rejected' => function ($reason, $index) {
}
]);
$promise = $pool->promise();
$promise->wait();
}
}

View file

@ -0,0 +1,169 @@
<?php
namespace App\Jobs\StoryPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Storage;
use App\Story;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\DeleteStory;
use App\Util\ActivityPub\Helpers;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use App\Util\ActivityPub\HttpSignature;
use App\Services\FollowerService;
use App\Services\StoryService;
class StoryExpire implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $story;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Story $story)
{
$this->story = $story;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$story = $this->story;
if($story->local == false) {
$this->handleRemoteExpiry();
return;
}
if($story->active == false) {
return;
}
if($story->expires_at->gt(now())) {
return;
}
$story->active = false;
$story->save();
$this->rotateMediaPath();
$this->fanoutExpiry();
StoryService::delLatest($story->profile_id);
}
protected function rotateMediaPath()
{
$story = $this->story;
$date = date('Y').date('m');
$old = $story->path;
$base = "story_archives/{$story->profile_id}/{$date}/";
$paths = explode('/', $old);
$path = array_pop($paths);
$newPath = $base . $path;
if(Storage::exists($old) == true) {
$dir = implode('/', $paths);
Storage::move($old, $newPath);
Storage::delete($old);
$story->bearcap_token = null;
$story->path = $newPath;
$story->save();
Storage::deleteDirectory($dir);
}
}
protected function fanoutExpiry()
{
$story = $this->story;
$profile = $story->profile;
if($story->local == false || $story->remote_url) {
return;
}
$audience = FollowerService::softwareAudience($story->profile_id, 'pixelfed');
if(empty($audience)) {
// Return on profiles with no remote followers
return;
}
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($story, new DeleteStory());
$activity = $fractal->createData($resource)->toArray();
$payload = json_encode($activity);
$client = new Client([
'timeout' => config('federation.activitypub.delivery.timeout')
]);
$requests = function($audience) use ($client, $activity, $profile, $payload) {
foreach($audience as $url) {
$headers = HttpSignature::sign($profile, $url, $activity);
yield function() use ($client, $url, $headers, $payload) {
return $client->postAsync($url, [
'curl' => [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HEADER => true
]
]);
};
}
};
$pool = new Pool($client, $requests($audience), [
'concurrency' => config('federation.activitypub.delivery.concurrency'),
'fulfilled' => function ($response, $index) {
},
'rejected' => function ($reason, $index) {
}
]);
$promise = $pool->promise();
$promise->wait();
}
protected function handleRemoteExpiry()
{
$story = $this->story;
$story->active = false;
$story->save();
$path = $story->path;
if(Storage::exists($path) == true) {
Storage::delete($path);
}
$story->views()->delete();
$story->delete();
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace App\Jobs\StoryPipeline;
use Cache, Log;
use App\Story;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\CreateStory;
use App\Util\ActivityPub\Helpers;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use App\Util\ActivityPub\HttpSignature;
use App\Services\FollowerService;
use App\Services\StoryService;
class StoryFanout implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $story;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Story $story)
{
$this->story = $story;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$story = $this->story;
$profile = $story->profile;
if($story->local == false || $story->remote_url) {
return;
}
StoryService::delLatest($story->profile_id);
$audience = FollowerService::softwareAudience($story->profile_id, 'pixelfed');
if(empty($audience)) {
// Return on profiles with no remote followers
return;
}
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($story, new CreateStory());
$activity = $fractal->createData($resource)->toArray();
$payload = json_encode($activity);
$client = new Client([
'timeout' => config('federation.activitypub.delivery.timeout')
]);
$requests = function($audience) use ($client, $activity, $profile, $payload) {
foreach($audience as $url) {
$headers = HttpSignature::sign($profile, $url, $activity);
yield function() use ($client, $url, $headers, $payload) {
return $client->postAsync($url, [
'curl' => [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HEADER => true
]
]);
};
}
};
$pool = new Pool($client, $requests($audience), [
'concurrency' => config('federation.activitypub.delivery.concurrency'),
'fulfilled' => function ($response, $index) {
},
'rejected' => function ($reason, $index) {
}
]);
$promise = $pool->promise();
$promise->wait();
}
}

View file

@ -0,0 +1,144 @@
<?php
namespace App\Jobs\StoryPipeline;
use Cache, Log;
use App\Story;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Util\ActivityPub\Helpers;
use App\Services\FollowerService;
use App\Util\Lexer\Bearcap;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;
use App\Util\ActivityPub\Validator\StoryValidator;
use App\Services\StoryService;
use App\Services\MediaPathService;
use Illuminate\Support\Str;
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;
class StoryFetch implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $activity;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($activity)
{
$this->activity = $activity;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$activity = $this->activity;
$activityId = $activity['id'];
$activityActor = $activity['actor'];
if(parse_url($activityId, PHP_URL_HOST) !== parse_url($activityActor, PHP_URL_HOST)) {
return;
}
$bearcap = Bearcap::decode($activity['object']['object']);
if(!$bearcap) {
return;
}
$url = $bearcap['url'];
$token = $bearcap['token'];
if(parse_url($activityId, PHP_URL_HOST) !== parse_url($url, PHP_URL_HOST)) {
return;
}
$version = config('pixelfed.version');
$appUrl = config('app.url');
$headers = [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token,
'User-Agent' => "(Pixelfed/{$version}; +{$appUrl})",
];
try {
$res = Http::withHeaders($headers)
->timeout(30)
->get($url);
} catch (RequestException $e) {
return false;
} catch (ConnectionException $e) {
return false;
} catch (\Exception $e) {
return false;
}
$payload = $res->json();
if(StoryValidator::validate($payload) == false) {
return;
}
if(Helpers::validateUrl($payload['attachment']['url']) == false) {
return;
}
$type = $payload['attachment']['type'] == 'Image' ? 'photo' : 'video';
$profile = Helpers::profileFetch($payload['attributedTo']);
$ext = pathinfo($payload['attachment']['url'], PATHINFO_EXTENSION);
$storagePath = MediaPathService::story($profile);
$fileName = Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $ext;
$contextOptions = [
'ssl' => [
'verify_peer' => false,
'verify_peername' => false
]
];
$ctx = stream_context_create($contextOptions);
$data = file_get_contents($payload['attachment']['url'], false, $ctx);
$tmpBase = storage_path('app/remcache/');
$tmpPath = $profile->id . '-' . $fileName;
$tmpName = $tmpBase . $tmpPath;
file_put_contents($tmpName, $data);
$disk = Storage::disk(config('filesystems.default'));
$path = $disk->putFileAs($storagePath, new File($tmpName), $fileName, 'public');
$size = filesize($tmpName);
unlink($tmpName);
$story = new Story;
$story->profile_id = $profile->id;
$story->object_id = $payload['id'];
$story->size = $size;
$story->mime = $payload['attachment']['mediaType'];
$story->duration = $payload['duration'];
$story->media_url = $payload['attachment']['url'];
$story->type = $type;
$story->public = false;
$story->local = false;
$story->active = true;
$story->path = $path;
$story->view_count = 0;
$story->can_reply = $payload['can_reply'];
$story->can_react = $payload['can_react'];
$story->created_at = now()->parse($payload['published']);
$story->expires_at = now()->parse($payload['expiresAt']);
$story->save();
StoryService::delLatest($story->profile_id);
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace App\Jobs\StoryPipeline;
use App\Story;
use App\Status;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Util\ActivityPub\Helpers;
class StoryReactionDeliver implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $story;
protected $status;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Story $story, Status $status)
{
$this->story = $story;
$this->status = $status;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$story = $this->story;
$status = $this->status;
if($story->local == true) {
return;
}
$target = $story->profile;
$actor = $status->profile;
$to = $target->inbox_url;
$payload = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $status->permalink(),
'type' => 'Story:Reaction',
'to' => $target->permalink(),
'actor' => $actor->permalink(),
'content' => $status->caption,
'inReplyTo' => $story->object_id,
'published' => $status->created_at->toAtomString()
];
Helpers::sendSignedObject($actor, $to, $payload);
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace App\Jobs\StoryPipeline;
use App\Story;
use App\Status;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Util\ActivityPub\Helpers;
class StoryReplyDeliver implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $story;
protected $status;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Story $story, Status $status)
{
$this->story = $story;
$this->status = $status;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$story = $this->story;
$status = $this->status;
if($story->local == true) {
return;
}
$target = $story->profile;
$actor = $status->profile;
$to = $target->inbox_url;
$payload = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $status->permalink(),
'type' => 'Story:Reply',
'to' => $target->permalink(),
'actor' => $actor->permalink(),
'content' => $status->caption,
'inReplyTo' => $story->object_id,
'published' => $status->created_at->toAtomString()
];
Helpers::sendSignedObject($actor, $to, $payload);
}
}

Some files were not shown because too many files have changed in this diff Show more