diff --git a/.env.example b/.env.example index 6fb3de6..45c0652 100644 --- a/.env.example +++ b/.env.example @@ -38,7 +38,7 @@ BROADCAST_CONNECTION=log FILESYSTEM_DISK=local QUEUE_CONNECTION=database -CACHE_STORE=database +CACHE_STORE=redis CACHE_PREFIX= MEMCACHED_HOST=127.0.0.1 diff --git a/README.md b/README.md index 179bc3a..1f5c889 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,67 @@ -# OurSpace +![Version](https://img.shields.io/badge/version-0.0.1-blue.svg?style=for-the-badge) +![Status](https://img.shields.io/badge/status-active-green.svg?style=for-the-badge) +![License](https://img.shields.io/badge/license-GPLv3-lightgrey.svg?style=for-the-badge) +![ActivityPub](https://img.shields.io/badge/protocol-ActivityPub-orange.svg?style=for-the-badge) +![Contributors](https://img.shields.io/github/contributors/0xd011f4ce/OurSpace?style=for-the-badge) +![Forks](https://img.shields.io/github/forks/0xd011f4ce/OurSpace?style=for-the-badge) +![Stargazers](https://img.shields.io/github/stars/0xd011f4ce/OurSpace?style=for-the-badge) +![Issues](https://img.shields.io/github/issues/0xd011f4ce/OurSpace?style=for-the-badge) -OurSpace is supposed to work like a clone of MySpace while being completely decentralised (based in the activitypub protocol). +
-Notice that the styles were taken from [AnySpace](https://anyspace.3to.moe/about.php) +
+

OurSpace

-## Overview +

A decentralised clone of MySpace based in the ActivityPub protocol.

+
+ +
+ Table of Contents + +
    +
  1. + About The Project + +
  2. +
  3. + Getting Started + +
  4. +
  5. + TODO +
  6. +
+
+ +## About The Project ![](img/OurSpaceHome.png) -![](img/OurSpaceProfile.png) + +**OurSpace** is a decentralised social networking platform inspired by the early concepts of MySpace but built using the ActivityPub protocol for enhanced privacy and interoperability. The goal of OurSpace is to provide users with a space to express themselves, share content, and connect with others in a federated environment that prioritises user privacy and data ownership. + +### Built With + +- ![Laravel](https://img.shields.io/badge/Laravel-v10-FF2D20?style=for-the-badge&logo=laravel&logoColor=white) +- ![PHP](https://img.shields.io/badge/PHP-v8-777BB4?style=for-the-badge&logo=php&logoColor=white) +- ![Redis](https://img.shields.io/badge/redis-%23DD0031.svg?style=for-the-badge&logo=redis&logoColor=white) + +## Getting Started + +OurSpace is meant to be easy to set up and run on your own server. Below are the steps to get started with OurSpace. + +### Prerequisites + +TODO: write this + +### Installation + +TODO: Write this ## TODO: -- [-] Activitypub - - [x] Accounts - - [-] Posts - - [x] Local posts should be federated - - [x] Local posts should be deleted - - [x] Remote posts should be fetched - - [x] Remote posts should be deleted - - [x] Follows - - [x] I cannot follow myself - - [ ] Check when waiting for approval - - [ ] Handle Rejection - - [x] Likes - - [x] Comments - - [ ] Boosts - - [x] Tags - - [ ] Pinned Posts - -- [-] Social features - - [x] Profile - - [ ] Show when the user is online - - [ ] Set mood - - [x] Set interests - - [x] Update profile picture - - [ ] Mark account as private (in federation manual approval is needed) - - [ ] Allow custom CSS - - [ ] Profile audio - - [ ] Top 8 friends - - [x] Friends (they are known as follows in the activitypub protocol) - - [x] Add friends - - [x] Remove friends - - [x] Posts (everything should be federated) - - [x] Create posts - - [x] Delete posts - - [x] Like posts - - [x] Comment posts - - [ ] Boost posts - - [x] Post tags - - [ ] Blog - - [ ] Bulletin - - [ ] Groups - - [ ] Forum - - [ ] Events - - [ ] Add users to favourites - - [ ] Chat - -- [ ] Others - - [ ] Music - - [ ] Announcements - -- [ ] Fixes - - [ ] Fix that weird json encoding in the object field of an activity +For a list of planned features and improvements, please refer to the [TODO](TODO) file. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..59f571a --- /dev/null +++ b/TODO.md @@ -0,0 +1,53 @@ +# TODO: + +- [-] Activitypub + - [x] Accounts + - [-] Posts + - [x] Local posts should be federated + - [x] Local posts should be deleted + - [x] Remote posts should be fetched + - [x] Remote posts should be deleted + - [x] Follows + - [x] I cannot follow myself + - [ ] Check when waiting for approval + - [ ] Handle Rejection + - [x] Likes + - [x] Comments + - [ ] Boosts + - [x] Tags + - [ ] Pinned Posts + +- [-] Social features + - [x] Profile + - [x] Show when the user is online + - [x] Set mood + - [x] Set interests + - [x] Update profile picture + - [ ] Mark account as private (in federation manual approval is needed) + - [x] Allow custom CSS + - [ ] Profile audio + - [ ] Top 8 friends + - [x] Friends (they are known as follows in the activitypub protocol) + - [x] Add friends + - [x] Remove friends + - [x] Posts (everything should be federated) + - [x] Create posts + - [x] Delete posts + - [x] Like posts + - [x] Comment posts + - [ ] Boost posts + - [x] Post tags + - [ ] Blog + - [ ] Bulletin + - [ ] Groups + - [ ] Forum + - [ ] Events + - [ ] Add users to favourites + - [ ] Chat + +- [ ] Others + - [ ] Music + - [ ] Announcements + +- [ ] Fixes + - [ ] Fix that weird json encoding in the object field of an activity diff --git a/app/Http/Middleware/UpdateOnlineUserMiddleware.php b/app/Http/Middleware/UpdateOnlineUserMiddleware.php new file mode 100644 index 0000000..529d6c7 --- /dev/null +++ b/app/Http/Middleware/UpdateOnlineUserMiddleware.php @@ -0,0 +1,36 @@ +check ()) + return $next($request); + + $user = auth ()->user (); + + if (Cache::has ("user-online-" . $user->id)) + return $next($request); + + $expires_at = now ()->addMinutes (5); + Cache::put ("user-online-" . $user->id, true, $expires_at); + + $user->last_online_at = now (); + $user->save (); + + return $next($request); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index deaab6a..04cea8d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Support\Facades\Cache; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -51,6 +52,10 @@ class User extends Authenticatable 'remember_token', ]; + protected $casts = [ + "last_online_at" => "datetime", + ]; + /** * Get the attributes that should be cast. * @@ -76,6 +81,11 @@ class User extends Authenticatable return $this->hasOne (Actor::class); } + public function is_online () + { + return Cache::has ("user-online-" . $this->id); + } + public function mutual_friends () { $followers = Follow::where ("actor", $this->actor->id)->pluck ("object")->toArray (); diff --git a/bootstrap/app.php b/bootstrap/app.php index 88c523d..3afe501 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,7 @@ validateCsrfTokens (except: [ "ap/v1/*", ]); + + $middleware->alias (["update_online" => UpdateOnlineUserMiddleware::class]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/composer.json b/composer.json index c69544b..39017bf 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "intervention/image": "^3.10", "laravel/framework": "^11.31", "laravel/tinker": "^2.9", - "league/html-to-markdown": "^5.1" + "league/html-to-markdown": "^5.1", + "predis/predis": "^2.3" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 9fa84cc..107f1a6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "628d31471da81db14ba13b073eaa09e0", + "content-hash": "6a6018401a3aeb6bad801ca5ed105225", "packages": [ { "name": "brick/math", @@ -2816,6 +2816,67 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "predis/predis", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/predis/predis.git", + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/predis/predis/zipball/bac46bfdb78cd6e9c7926c697012aae740cb9ec9", + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^8.0 || ^9.4" + }, + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" + } + ], + "description": "A flexible and feature-complete Redis client for PHP.", + "homepage": "http://github.com/predis/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2024-11-21T20:00:02+00:00" + }, { "name": "psr/clock", "version": "1.0.0", diff --git a/database/migrations/2025_01_05_214435_add_fields_to_users_table.php b/database/migrations/2025_01_05_214435_add_fields_to_users_table.php index dc29fc8..5169f6d 100644 --- a/database/migrations/2025_01_05_214435_add_fields_to_users_table.php +++ b/database/migrations/2025_01_05_214435_add_fields_to_users_table.php @@ -14,6 +14,7 @@ return new class extends Migration Schema::table('users', function (Blueprint $table) { $table->boolean ("is_admin")->default (false); $table->boolean ("is_mod")->default (false); + $table->timestamp ("last_online_at")->nullable (); }); } @@ -25,6 +26,7 @@ return new class extends Migration Schema::table('users', function (Blueprint $table) { $table->dropColumn ("is_admin"); $table->dropColumn ("is_mod"); + $table->dropColumn ("last_online_at"); }); } }; diff --git a/resources/views/home_loggedin.blade.php b/resources/views/home_loggedin.blade.php index 8a5bf0d..77ebd42 100644 --- a/resources/views/home_loggedin.blade.php +++ b/resources/views/home_loggedin.blade.php @@ -1,7 +1,3 @@ -@php - -@endphp -
diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index facd2d0..7c7955a 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -24,14 +24,14 @@

{{ $user->status }}

{{ $user->about_you }}

- @if (auth ()->user () && auth ()->user ()->actor ()->first ()->is ($actor)) -

- online YOU! -

- @else + @if ($user->is_online ())

online ONLINE!

+ @else +

+ Last online: {{ $user->last_online_at->diffForHumans () }} +

@endif

Joined: {{ $user->created_at->diffForHumans () }}

diff --git a/routes/web.php b/routes/web.php index de94391..a047e0a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,8 +8,6 @@ use App\Http\Controllers\UserController; use App\Http\Controllers\ProfileController; use App\Http\Controllers\UserActionController; -Route::get('/', [ HomeController::class, "home" ])->name ("home"); - // auth related Route::get ("/auth/login", [ UserController::class, "login" ])->name ("login")->middleware ("guest"); Route::get ("/auth/signup", [ UserController::class, "signup" ])->name ("signup")->middleware ("guest"); @@ -22,24 +20,28 @@ Route::post ("/user/action/friend", [ UserActionController::class, "friend" ])-> Route::post ("/user/action/unfriend", [ UserActionController::class, "unfriend" ])->name ("user.unfriend")->middleware ("auth"); Route::post ("/user/action/post/new", [ UserActionController::class, "post_new" ])->name ("user.post.new")->middleware ("auth"); -// user routes -Route::get ("/user/edit", [ ProfileController::class, "edit" ])->name ("users.edit")->middleware ("auth"); -Route::post ("/user/edit", [ ProfileController::class, "update" ])->middleware ("auth"); -Route::get ("/user/{user_name}/friends", [ ProfileController::class, "friends" ])->name ("users.friends"); -Route::get ("/user/{user_name}", [ ProfileController::class, "show" ])->name ("users.show"); +Route::middleware ("update_online")->group (function () { + Route::get('/', [ HomeController::class, "home" ])->name ("home"); -// posts routes -Route::get ("/post/{note}/edit", [ PostController::class, "edit" ])->name ("posts.edit")->middleware ("auth"); -Route::post ("/post/{note}/edit", [ PostController::class, "update" ])->middleware ("auth"); -Route::post ("/post/{note}/like", [ PostController::class, "like" ])->name ("posts.like")->middleware ("auth"); -Route::get ("/post/{note}", [ PostController::class, "show" ])->name ("posts.show"); -Route::delete ("/post/{note}", [ PostController::class, "delete" ])->name ("posts.delete")->middleware ("auth"); + // user routes + Route::get ("/user/edit", [ ProfileController::class, "edit" ])->name ("users.edit")->middleware ("auth"); + Route::post ("/user/edit", [ ProfileController::class, "update" ])->middleware ("auth"); + Route::get ("/user/{user_name}/friends", [ ProfileController::class, "friends" ])->name ("users.friends"); + Route::get ("/user/{user_name}", [ ProfileController::class, "show" ])->name ("users.show"); -// other routes -Route::get ("/browse", [ HomeController::class, "browse" ])->name ("browse"); -Route::get ("/tags/{tag}", [ HomeController::class, "tag" ])->name ("tags"); // TODO: This -Route::get ("/search", [ HomeController::class, "search" ])->name ("search"); -Route::get ("/requests", [ HomeController::class, "requests" ])->name ("requests")->middleware ("auth"); -Route::post ("/requests", [ HomeController::class, "requests_accept" ])->middleware ("auth"); + // posts routes + Route::get ("/post/{note}/edit", [ PostController::class, "edit" ])->name ("posts.edit")->middleware ("auth"); + Route::post ("/post/{note}/edit", [ PostController::class, "update" ])->middleware ("auth"); + Route::post ("/post/{note}/like", [ PostController::class, "like" ])->name ("posts.like")->middleware ("auth"); + Route::get ("/post/{note}", [ PostController::class, "show" ])->name ("posts.show"); + Route::delete ("/post/{note}", [ PostController::class, "delete" ])->name ("posts.delete")->middleware ("auth"); + + // other routes + Route::get ("/browse", [ HomeController::class, "browse" ])->name ("browse"); + Route::get ("/tags/{tag}", [ HomeController::class, "tag" ])->name ("tags"); // TODO: This + Route::get ("/search", [ HomeController::class, "search" ])->name ("search"); + Route::get ("/requests", [ HomeController::class, "requests" ])->name ("requests")->middleware ("auth"); + Route::post ("/requests", [ HomeController::class, "requests_accept" ])->middleware ("auth"); +}); require __DIR__ . "/api.php";