finished the online feature

This commit is contained in:
Ghostie 2025-01-05 18:35:37 -05:00
parent c578a4c56f
commit dcf5c9a8ac
12 changed files with 255 additions and 87 deletions

View File

@ -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

115
README.md
View File

@ -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).
<br>
Notice that the styles were taken from [AnySpace](https://anyspace.3to.moe/about.php)
<div align="center">
<h3 align="center">OurSpace</h3>
## Overview
<p align="center">A decentralised clone of MySpace based in the ActivityPub protocol.</p>
</div>
<details>
<summary>Table of Contents</summary>
<ol>
<li>
<a href="#about-the-project">About The Project</a>
<ul>
<li><a href="#built-with">Built With</a></li>
</ul>
</li>
<li>
<a href="#getting-started">Getting Started</a>
<ul>
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#installation">Installation</a></li>
</ul>
</li>
<li>
<a href="#todo">TODO</a>
</li>
</ol>
</details>
## 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.

53
TODO.md Normal file
View File

@ -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

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Auth;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
class UpdateOnlineUserMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (!auth ()->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);
}
}

View File

@ -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 ();

View File

@ -1,5 +1,7 @@
<?php
use App\Http\Middleware\UpdateOnlineUserMiddleware;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
@ -14,6 +16,8 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->validateCsrfTokens (except: [
"ap/v1/*",
]);
$middleware->alias (["update_online" => UpdateOnlineUserMiddleware::class]);
})
->withExceptions(function (Exceptions $exceptions) {
//

View File

@ -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",

63
composer.lock generated
View File

@ -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",

View File

@ -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");
});
}
};

View File

@ -1,7 +1,3 @@
@php
@endphp
<div class="row profile user-home">
<div class="col w-40 left">
<div class="general-about home-actions">

View File

@ -24,14 +24,14 @@
<div class="details">
<p>{{ $user->status }}</p>
<p>{{ $user->about_you }}</p>
@if (auth ()->user () && auth ()->user ()->actor ()->first ()->is ($actor))
<p class="online">
<img loading="lazy" src="/resources/img/green_person.png" alt="online"> YOU!
</p>
@else
@if ($user->is_online ())
<p class="online">
<img loading="lazy" src="/resources/img/green_person.png" alt="online"> ONLINE!
</p>
@else
<p>
<b>Last online: </b> {{ $user->last_online_at->diffForHumans () }}
</p>
@endif
<p><b>Joined: </b> {{ $user->created_at->diffForHumans () }}</p>
</div>

View File

@ -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";