mirror of
https://koodu.h-i.works/projects/thebadspace
synced 2025-05-06 14:41:02 -05:00
Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d310cf4fb9 | ||
|
fed8748923 | ||
|
04ac27ea04 | ||
|
4dafad447d | ||
|
d165d0c2bd | ||
|
3e070676e4 | ||
|
c9c3b88f4d | ||
|
66b43b2d4e | ||
|
c2be80c005 | ||
|
7cdd284968 | ||
|
7f7f324163 | ||
|
67d40bd166 | ||
|
aeaca3e0f0 | ||
|
0ad5208638 | ||
|
8ed702bd59 | ||
|
a0fee9b6b1 | ||
|
cf11d246d1 | ||
|
2c88d45e52 | ||
|
5515503117 | ||
|
0f16b275a0 | ||
|
d138198305 | ||
|
c0e05c13e6 | ||
|
dbea3ff219 | ||
|
1420d61892 | ||
|
bfce3c82ec | ||
|
3e937c5083 | ||
|
b7e2bbb334 | ||
|
56f445572f | ||
|
515de4c56b | ||
|
73a0abf898 | ||
|
07793a413a | ||
|
8fbf927f2d | ||
|
8ce9a7744a | ||
|
f7bd675b5d | ||
|
ccd0a7a3a9 | ||
|
80191a2e46 | ||
|
8353d154c4 | ||
|
098cd72186 | ||
|
31f45c4af5 | ||
|
3c0762344e | ||
|
d0c8def297 | ||
|
99e22f5697 | ||
|
2932af0d3f | ||
|
0eeab6355e | ||
|
43e0004ac5 | ||
|
d3d1d56680 | ||
|
0a02a71983 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,8 @@
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/reference
|
||||
/public/assets/images/references
|
||||
/public/assets/images/members
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
|
@ -55,20 +55,15 @@ return $config
|
||||
]
|
||||
],
|
||||
'no_multiline_whitespace_around_double_arrow' => true,
|
||||
'no_spaces_around_offset' => true,
|
||||
'no_unused_imports' => true,
|
||||
'no_whitespace_before_comma_in_array' => true,
|
||||
'no_whitespace_in_blank_line' => true,
|
||||
'object_operator_without_whitespace' => true,
|
||||
'single_blank_line_before_namespace' => true,
|
||||
'ternary_operator_spaces' => true,
|
||||
'trim_array_spaces' => true,
|
||||
'unary_operator_spaces' => true,
|
||||
'whitespace_after_comma_in_array' => true,
|
||||
'single_line_after_imports' => true,
|
||||
'ordered_imports' => [
|
||||
'sort_algorithm' => 'none',
|
||||
],
|
||||
//'single_blank_line_before_namespace' => true, php fixer doesn't like this rule?
|
||||
//Other rules here...
|
||||
])
|
||||
->setLineEnding("\n");
|
||||
|
88
README.md
88
README.md
@ -1,9 +1,91 @@
|
||||
# The Bad Space
|
||||
|
||||
A searcable catalog of the worst places on the web.
|
||||
As the fediverse is a largely unregulated space, The Bad Space arose from a need for collaborative safety tools that can be used identify instances that house bad actors, are poorly moderated, and/or contain inappropriate/offensive content (CSAM, hate speech, fascist ideology, etc.) that puts marginalized communities at risk for harassment and abuse.
|
||||
|
||||
More features incoming
|
||||
The goal of TBS is to provide a way for communities to work together based on their specific needs and not a one-size-fits-all moderation approach for a more tailored and nuanced experience.
|
||||
|
||||
An Hi Project =)
|
||||
## Requirements
|
||||
TBS is built with the [Laravel](https://laravel.com/) framework, so PHP 8.2 and [Composer](https://getcomposer.org/doc/00-intro.md) is required, as well as [PostgreSQL](https://www.postgresql.org/download/) 14+ for the database. [Git](https://git-scm.com/downloads) is not strictly required as the code base can be manually downloaded, but it does make installing and updating easier.
|
||||
|
||||
*NOTE*: On Linux, some additional PHP extensions need to be installed, but don't worry it's easy. Run `sudo apt-get install php-mbstring`, `sudo apt-get install php-xml` and `sudo apt-get install php-zip `to get them and you're good to go.
|
||||
|
||||
*NOTE*: Environment set up on a local machine can be a pain in the ass, so to make that easier [Herd](https://herd.laravel.com/) can be used for MacOS and Windows and [FlyEnv](https://www.macphpstudy.com/) for Linux folks that don't want to do it all by hand. [DBNgin](https://dbngin.com/) can be used to make database setup painless. Yes, and they are all free.
|
||||
|
||||
## Install
|
||||
Use `git clone https://koodu.h-i.works/projects/thebadspace.git` to install or just go grab the zip from the repo and unzip it.
|
||||
|
||||
Create your database in PostgreSQL and remember the name of the database and the username and password used to access it.
|
||||
|
||||
Go into the folder through your terminal and run `composer install` to grab all the dependencies needed and create the `.env` file that stores all project specific variables.
|
||||
|
||||
Open the `.env` file and add your database information so it resembles the following:
|
||||
|
||||
```
|
||||
DB_CONNECTION=pgsql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=your_database_name
|
||||
DB_USERNAME=database_username
|
||||
DB_PASSWORD=database_password
|
||||
```
|
||||
|
||||
Now that the app is aware of what database is being used, it's time to set up the tables.
|
||||
|
||||
Back in your terminal, run `php artisan migrate` and if your database is set up correctly, all the tables will be created automatically.
|
||||
|
||||
Congrats, you're all set up.
|
||||
|
||||
## Usage
|
||||
TBS can be run locally or on a remote server.
|
||||
|
||||
To get up and running on your local machine, hop back in your terminal and run `php -S localhost:8000 -t public/ ` in the folder of the project.
|
||||
|
||||
If you're running it on a server, use these configs to get going.
|
||||
|
||||
**For Nginx**
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name yourcoolassdomain.com;
|
||||
client_max_body_size 20M //Change to whatever to limit/increase file upload size
|
||||
location / {
|
||||
try_files $uri /index.php$is_args$args;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**For Apache**
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin admin@yourcoolassdomain.com
|
||||
ServerName yourcoolassdomain.com
|
||||
ServerAlias www.yourcoolassdomain.com
|
||||
DocumentRoot /path-to-fipamo-folder/public
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Remember to restart Apache/NGINX once these configs have been plugged in.
|
||||
|
||||
Go the root domain of the site you just set up (or http://localhost:8000 if running locally) and you'll see a screen to set up an admin account.
|
||||
|
||||
Once the admin account has been created, go to The Den section in the menu and login in.
|
||||
|
||||
### Adding Sources
|
||||
The core of TBS is adding sources of the instances (currently just Mastodon, but more coming soon) to be used to compile exportable block lists. *Note:* While many instance block lists are publicly available, it is good practice to ask for permission to use them.
|
||||
|
||||
Sources can be added under the `Manage Sources` link. Fill in the appropriate information and then save. *Note:* Some instances keep their block lists private for safety reasons, so you will need an access token to use them. These tokens cannot be created by TBS and must be obtained from the instance itself.
|
||||
|
||||
Once everything is filled in, save the source.
|
||||
|
||||
To remove a source from the compilation process, mark its status as active and save.
|
||||
|
||||
## Compiling Sources
|
||||
Now it's time to grab the block list data from the individual sources entered needed to compile curation information.
|
||||
|
||||
This can be done under `Manage Locations` on the Den front page. Click `Update Source Data` to grab block list info from the sources added earlier.
|
||||
|
||||
When that is completed, then click `Update Locations Data` to compile individual sources data into one that the site uses to display info about moderated instances. If this is being run for the first time, this must be clicked *twice*.
|
||||
|
||||
Once that's done, go to the main domain again, and you'll see the results of the compilation process.
|
||||
|
@ -34,14 +34,6 @@ class DenController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function member(Request $request)
|
||||
{
|
||||
$member = Auth::user();
|
||||
return view('back.member', [
|
||||
'handle' => $member->handle,
|
||||
'title' => "Manage Members"]);
|
||||
}
|
||||
|
||||
public function locations(Request $request)
|
||||
{
|
||||
$member = Auth::user();
|
||||
|
@ -46,12 +46,12 @@ class ExportController extends Controller
|
||||
$sources = Source::where("active", true)->get();
|
||||
if ($type == 'mastodon') {
|
||||
$columns = [
|
||||
'domain',
|
||||
'severity',
|
||||
'public_comment',
|
||||
'reject_media',
|
||||
'reject_reports',
|
||||
'obfuscate',
|
||||
'#domain',
|
||||
'#severity',
|
||||
'#public_comment',
|
||||
'#reject_media',
|
||||
'#reject_reports',
|
||||
'#obfuscate',
|
||||
];
|
||||
};
|
||||
|
||||
@ -60,13 +60,18 @@ class ExportController extends Controller
|
||||
if ($rate * 100 >= $percent) {
|
||||
if ($type == 'mastodon') {
|
||||
//comman break teh CSV so just take them out
|
||||
$comments = str_replace(",", ";", $location->description);
|
||||
$comments = str_replace(",", ";", $location->public_comments);
|
||||
|
||||
//remove extra white space
|
||||
$comments = str_replace(["\n\r", "\n", "\r"], " ", $comments);
|
||||
$comments = str_replace(['"', "'"], "", $comments);
|
||||
if ($location->rating == 'defederate') {
|
||||
$rating = 'suspend';
|
||||
} else {
|
||||
$rating = $location->rating;
|
||||
}
|
||||
//add to the export list
|
||||
array_push($list, [$location->url, $location->rating, $comments, "FALSE", "FALSE", "FALSE"]);
|
||||
array_push($list, [$location->url, $rating, $comments, "FALSE", "FALSE", "FALSE"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,33 +6,49 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Repositories\LocationRepository;
|
||||
use App\Repositories\SourceRepository;
|
||||
use App\Repositories\MemberRepository;
|
||||
use App\Services\PaginationService;
|
||||
|
||||
class FrontIndexController extends Controller
|
||||
{
|
||||
protected $location;
|
||||
protected $source;
|
||||
protected $member;
|
||||
protected $pagination;
|
||||
|
||||
public function __construct(
|
||||
LocationRepository $locationRepository,
|
||||
SourceRepository $sourceRepository,
|
||||
MemberRepository $memberRepository,
|
||||
PaginationService $paginationService
|
||||
) {
|
||||
$this->location = $locationRepository;
|
||||
$this->source = $sourceRepository;
|
||||
$this->member = $memberRepository;
|
||||
$this->pagination = $paginationService;
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
return view('front.index', [
|
||||
'count' => count($this->location->getActiveLocations()),
|
||||
'sources' => count($this->source->getActive()),
|
||||
'recent' => $this->location->getRecent(),
|
||||
'latest_date' => $this->location->getRecent()[0]->updated_at->format('Y M d'),
|
||||
'title' => "The Bad Space"
|
||||
]);
|
||||
//check to see if there are any accounts
|
||||
if (count($this->member->getAll()) == 0) {
|
||||
return view('back.member', [
|
||||
'mode' => 'admin-create',
|
||||
'title' => "Welcome, welcome"]);
|
||||
} else {
|
||||
//for fresh installs that dont have any source data yet
|
||||
$latest_update = 'Never Run';
|
||||
if (count($this->location->getRecent()) != 0) {
|
||||
$latest_update = $this->location->getRecent()[0]->updated_at->format('Y M d');
|
||||
}
|
||||
return view('front.index', [
|
||||
'count' => count($this->location->getActiveLocations()),
|
||||
'sources' => count($this->source->getActive()),
|
||||
'recent' => $this->location->getRecent(),
|
||||
'latest_date' => $latest_update,
|
||||
'title' => "The Bad Space"
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function indexSearch(Request $request)
|
||||
@ -76,15 +92,19 @@ class FrontIndexController extends Controller
|
||||
}
|
||||
|
||||
if (isset($member->role)) {
|
||||
($member->role == 1 || $member->role == 2) ? $edit = true : $edit = false;
|
||||
($member->role == 0 || $member->role == 1) ? $edit = true : $edit = false;
|
||||
}
|
||||
|
||||
$links = explode(',', $location->archive_links);
|
||||
$comments = explode('+', $location->public_comments);
|
||||
return view('front.location', [
|
||||
'title' => str_replace(".", " ", $name),
|
||||
'location' => $location,
|
||||
'comments' => $comments,
|
||||
'actions' => $location->block_count + $location->silence_count,
|
||||
'sources_count' => count($sources),
|
||||
'images' => json_decode($location->images),
|
||||
'links' => $links,
|
||||
'updated' => $location->updated_at->format('Y M d'),
|
||||
'edit' => $edit
|
||||
]);
|
||||
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\UpdateService;
|
||||
use App\Repositories\LocationRepository;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LocationController extends Controller
|
||||
{
|
||||
@ -18,34 +19,51 @@ class LocationController extends Controller
|
||||
$this->location = $locationRepository;
|
||||
}
|
||||
|
||||
//actions
|
||||
public function updateLocations()
|
||||
{
|
||||
$result = $this->update->data();
|
||||
|
||||
return back()->with(
|
||||
'message',
|
||||
$result
|
||||
);
|
||||
//role check
|
||||
$member = Auth::user();
|
||||
if ($member->role == 0) {
|
||||
$result = $this->update->data();
|
||||
return back()->with(
|
||||
'message',
|
||||
$result
|
||||
);
|
||||
} else {
|
||||
return back()->withErrors('message', 'Nah, you don\'t have permission to do this');
|
||||
}
|
||||
}
|
||||
|
||||
public function compileLocations()
|
||||
{
|
||||
$result = $this->update->list();
|
||||
|
||||
return back()->with(
|
||||
'message',
|
||||
$result
|
||||
);
|
||||
//role check
|
||||
$member = Auth::user();
|
||||
if ($member->role == 0) {
|
||||
$result = $this->update->list();
|
||||
return back()->with(
|
||||
'message',
|
||||
$result
|
||||
);
|
||||
} else {
|
||||
return back()->withErrors('message', 'Nah, you don\'t have permission to do this');
|
||||
}
|
||||
}
|
||||
|
||||
public function editLocation(Request $request)
|
||||
{
|
||||
$token = csrf_token();
|
||||
$response = $this->location->editLocation($request);
|
||||
if ($response['status']) {
|
||||
return back()->with('message', $response['message']);
|
||||
$token = csrf_token();
|
||||
//role check
|
||||
$member = Auth::user();
|
||||
if ($member->role == 0 || $member->role == 1) {
|
||||
$response = $this->location->editLocation($request);
|
||||
if ($response['status']) {
|
||||
return back()->with('message', $response['message']);
|
||||
} else {
|
||||
return back()->withErrors('message', $response['message']);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors('message', $response['message']);
|
||||
return back()->withErrors('message', 'Nah, you don\'t have permission to do this');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
182
app/Http/Controllers/MemberController.php
Normal file
182
app/Http/Controllers/MemberController.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Repositories\MemberRepository;
|
||||
|
||||
class MemberController extends Controller
|
||||
{
|
||||
protected $member;
|
||||
|
||||
public function __construct(
|
||||
MemberRepository $memberRepo
|
||||
) {
|
||||
$this->member = $memberRepo;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$member = Auth::user();
|
||||
return view('back.member', [
|
||||
'handle' => $member->handle,
|
||||
'members' => $this->member->getAll(),
|
||||
'mode' => 'index',
|
||||
'title' => "Manage Members"]);
|
||||
}
|
||||
|
||||
public function profile(Request $request)
|
||||
{
|
||||
$member = Auth::user();
|
||||
$avi = '';
|
||||
if ($member->avatar == 'default-member-avatar') {
|
||||
$avi = '/assets/images/global/default-avi.png';
|
||||
} else {
|
||||
$avi = $member->avatar;
|
||||
}
|
||||
return view('back.profile', [
|
||||
'title' => "Hey, it's you!",
|
||||
'handle' => $member->handle,
|
||||
'email' => $member->email,
|
||||
'avatar' => $avi,
|
||||
'pronouns' => $member->pronoun,
|
||||
'uuid' => $member->uuid,
|
||||
'role' => $member->role
|
||||
]);
|
||||
}
|
||||
|
||||
public function editMember(Request $request, $uuid = 0)
|
||||
{
|
||||
$member = $this->member->get($uuid);
|
||||
$avi = '';
|
||||
if ($member->avatar == 'default-member-avatar') {
|
||||
$avi = '/assets/images/global/default-avi.png';
|
||||
} else {
|
||||
$avi = $member->avatar;
|
||||
}
|
||||
return view('back.member', [
|
||||
'member' => $member,
|
||||
'avatar' => $avi,
|
||||
'mode' => 'member-edit',
|
||||
'title' => "Edit Member Info"]);
|
||||
}
|
||||
|
||||
public function createMember(Request $Request)
|
||||
{
|
||||
return view('back.member', [
|
||||
'mode' => 'member-create',
|
||||
'title' => "Make a new friend"]);
|
||||
}
|
||||
|
||||
//actions
|
||||
public function profileEdit(Request $request)
|
||||
{
|
||||
$token = csrf_token();
|
||||
//check if logged in member id matches profile request id
|
||||
$member = Auth::user();
|
||||
if ($member->uuid == $request->id) {
|
||||
//validate required fields
|
||||
$valid = $request->validate([
|
||||
'handle' => ['required'],
|
||||
'email' => ['required'],
|
||||
]);
|
||||
if ($valid) {
|
||||
$response = $this->member->editProfile($request);
|
||||
if ($response['status'] == true) {
|
||||
return back()->with('message', $response['message']);
|
||||
} else {
|
||||
return back()->withErrors([$response['message']]);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Misssing some required info, homie.']);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['This is not your profile to edit.']);
|
||||
}
|
||||
}
|
||||
|
||||
public function memberEdit(Request $request)
|
||||
{
|
||||
$token = csrf_token();
|
||||
//role check
|
||||
$member = Auth::user();
|
||||
if ($member->role == 0) {
|
||||
$valid = $request->validate([
|
||||
'handle' => ['required'],
|
||||
'email' => ['required'],
|
||||
'role' => ['required']
|
||||
]);
|
||||
|
||||
if ($valid) {
|
||||
$response = $this->member->edit($request);
|
||||
if ($response['status'] == true) {
|
||||
return back()->with('message', $response['message']);
|
||||
} else {
|
||||
return back()->withErrors([$response['message']]);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Misssing some required info, homie.']);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Nah, you can\'t do this. Wrong permissions.']);
|
||||
}
|
||||
}
|
||||
|
||||
public function memberCreate(Request $request)
|
||||
{
|
||||
$token = csrf_token();
|
||||
$member = Auth::user();
|
||||
if ($member->role == 0) {
|
||||
$valid = $request->validate([
|
||||
'handle' => ['required'],
|
||||
'email' => ['required'],
|
||||
'role' => ['required'],
|
||||
'pronouns' => ['required'],
|
||||
'fresh_pass' => ['required'],
|
||||
'fresh_pass_confirm' => ['required'],
|
||||
]);
|
||||
|
||||
if ($valid) {
|
||||
$response = $this->member->add($request);
|
||||
if ($response['status'] == true) {
|
||||
return redirect('/den/member')->with('message', $response['message']);
|
||||
} else {
|
||||
return back()->withErrors([$response['message']]);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Misssing some required info, homie.']);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Nah, you can\'t do this. Wrong permissions.']);
|
||||
}
|
||||
}
|
||||
|
||||
public function adminCreate(Request $request)
|
||||
{
|
||||
//should only be run of no members exist
|
||||
if (count($this->member->getAll()) == 0) {
|
||||
$token = csrf_token();
|
||||
$valid = $request->validate([
|
||||
'handle' => ['required'],
|
||||
'email' => ['required'],
|
||||
'pronouns' => ['required'],
|
||||
'fresh_pass' => ['required'],
|
||||
'fresh_pass_confirm' => ['required'],
|
||||
]);
|
||||
|
||||
if ($valid) {
|
||||
$response = $this->member->add($request);
|
||||
if ($response['status'] == true) {
|
||||
return redirect('/den/member')->with('message', $response['message']);
|
||||
} else {
|
||||
return back()->withErrors([$response['message']]);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Misssing some required info, homie.']);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Shame on you for even trying that.']);
|
||||
}
|
||||
}
|
||||
}
|
102
app/Http/Controllers/SourceController.php
Normal file
102
app/Http/Controllers/SourceController.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Repositories\SourceRepository;
|
||||
|
||||
class SourceController extends Controller
|
||||
{
|
||||
protected $sources;
|
||||
|
||||
public function __construct(
|
||||
SourceRepository $sourceRepo
|
||||
) {
|
||||
$this->sources = $sourceRepo;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$member = Auth::user();
|
||||
return view('back.sources', [
|
||||
'mode' => 'index',
|
||||
'handle' => $member->handle,
|
||||
'sources' => $this->sources->getAll(),
|
||||
'title' => "Manage Sources"]);
|
||||
}
|
||||
|
||||
public function editSource(Request $request, $id = 0)
|
||||
{
|
||||
$source = $this->sources->get($id);
|
||||
|
||||
return view('back.sources', [
|
||||
'mode' => 'source-edit',
|
||||
'source' => $source,
|
||||
'title' => "Edit Source Info"]);
|
||||
}
|
||||
|
||||
public function createSource(Request $Request)
|
||||
{
|
||||
return view('back.sources', [
|
||||
'mode' => 'source-create',
|
||||
'title' => "Enter a new Source"]);
|
||||
}
|
||||
|
||||
//actions
|
||||
|
||||
public function sourceEdit(Request $request)
|
||||
{
|
||||
$token = csrf_token();
|
||||
//role check
|
||||
$member = Auth::user();
|
||||
if ($member->role == 0) {
|
||||
$valid = $request->validate([
|
||||
'url' => ['required'],
|
||||
'type' => ['required'],
|
||||
'status' => ['required'],
|
||||
'format' => ['required'],
|
||||
]);
|
||||
|
||||
if ($valid) {
|
||||
$response = $this->sources->edit($request);
|
||||
if ($response['status'] == true) {
|
||||
return back()->with('message', $response['message']);
|
||||
} else {
|
||||
return back()->withErrors([$response['message']]);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Misssing some required info, homie.']);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Nah, you can\'t do this. Wrong permissions.']);
|
||||
}
|
||||
}
|
||||
|
||||
public function sourceCreate(Request $request)
|
||||
{
|
||||
$token = csrf_token();
|
||||
$member = Auth::user();
|
||||
if ($member->role == 0) {
|
||||
$valid = $request->validate([
|
||||
'url' => ['required'],
|
||||
'type' => ['required'],
|
||||
'status' => ['required'],
|
||||
'format' => ['required'],
|
||||
]);
|
||||
|
||||
if ($valid) {
|
||||
$response = $this->sources->add($request);
|
||||
if ($response['status'] == true) {
|
||||
return redirect('/den/sources')->with('message', $response['message']);
|
||||
} else {
|
||||
return back()->withErrors([$response['message']]);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Misssing some required info, homie.']);
|
||||
}
|
||||
} else {
|
||||
return back()->withErrors(['Nah, you can\'t do this. Wrong permissions.']);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ class Location extends Model
|
||||
"uuid",
|
||||
"name",
|
||||
"url",
|
||||
"description",
|
||||
"public_comments",
|
||||
"images",
|
||||
"active",
|
||||
"rating",
|
||||
@ -35,6 +35,16 @@ class Location extends Model
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"actions_count",
|
||||
"archive_links"
|
||||
"archive_links",
|
||||
"notes",
|
||||
];
|
||||
|
||||
public function scopeSearch($query, $search)
|
||||
{
|
||||
if (!$search) {
|
||||
return $query;
|
||||
}
|
||||
return $query->whereRaw('searchtext @@ to_tsquery(\'english\', ?)', [$search])
|
||||
->orderByRaw('ts_rank(searchtext, to_tsquery(\'english\', ?)) DESC', [$search]);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,20 @@ class Member extends Authenticatable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = "member";
|
||||
protected $fillable = ["uuid", "handle", "email", "password", "active", "role", "avatar", "pronoun", "gender"];
|
||||
public $timestamps = false;
|
||||
protected $table = "member";
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = true;
|
||||
protected $fillable = [
|
||||
"uuid",
|
||||
"handle",
|
||||
"email",
|
||||
"password",
|
||||
"active",
|
||||
"role",
|
||||
"avatar",
|
||||
"pronoun",
|
||||
"created_at",
|
||||
"last_login"
|
||||
];
|
||||
}
|
||||
|
@ -5,10 +5,12 @@ namespace App\Providers;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Repositories\LocationRepository;
|
||||
use App\Repositories\SourceRepository;
|
||||
use App\Repositories\MemberRepository;
|
||||
use App\Services\UpdateService;
|
||||
use App\Services\MaintenanceService;
|
||||
use App\Models\Location;
|
||||
use App\Models\Source;
|
||||
use App\Models\Member;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -25,6 +27,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
return new SourceRepository(new Source());
|
||||
});
|
||||
|
||||
$this->app->bind(MemberRepository::class, function ($app) {
|
||||
return new MemberRepository(new Member());
|
||||
});
|
||||
|
||||
$this->app->bind(UpdateService::class, function ($app) {
|
||||
return new UpdateService(
|
||||
new LocationRepository(new Location()),
|
||||
|
@ -23,7 +23,8 @@ class LocationRepository
|
||||
$rawSearch = $terms;
|
||||
$terms = str_replace(",", "", $terms);
|
||||
$terms = str_replace(" ", "|", $terms);
|
||||
$raw = DB::select("SELECT * FROM searchlocations(?)", [$terms]);
|
||||
//$raw = DB::select("SELECT * FROM searchlocations(?)", [$terms]);
|
||||
$raw = Location::search($terms)->get();
|
||||
$results = [];
|
||||
foreach ($raw as $item) {
|
||||
if (($item->block_count + $item->silence_count) >= 2) {
|
||||
@ -56,24 +57,34 @@ class LocationRepository
|
||||
|
||||
public function editLocation($request)
|
||||
{
|
||||
$location = $this->getLocation($request->id);
|
||||
$images = [];
|
||||
$location = $this->getLocation($request->id);
|
||||
$publicPath = '../public/';
|
||||
$refPath = 'assets/images/references/' . $location->uuid;
|
||||
$images = [];
|
||||
if ($request->hasfile("references")) {
|
||||
foreach ($request->references as $file) {
|
||||
$path = $file->store('reference');
|
||||
array_push($images, ["path" => $path]);
|
||||
if (!is_dir($publicPath . $refPath)) {
|
||||
mkdir($publicPath . $refPath, 0755, true);
|
||||
}
|
||||
$filename = urlencode($file->getClientOriginalName());
|
||||
$file->move($publicPath . $refPath, $filename);
|
||||
//$path = $file->store('reference');
|
||||
array_push($images, ["path" => '/' . $refPath . '/' . $filename]);
|
||||
}
|
||||
}
|
||||
$request->merge(['images' => json_encode($images)]);
|
||||
if (!empty($images)) {
|
||||
$request->merge(['images' => json_encode($images)]);
|
||||
$location->images = json_encode($images);
|
||||
}
|
||||
|
||||
$location->name = $request->name;
|
||||
$location->description = $request->description;
|
||||
$location->notes = $request->notes;
|
||||
$location->archive_links = $request->archive_links;
|
||||
$location->images = json_encode($images);
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($location->save()) {
|
||||
return ['status' => true, 'message' => "Location Editited -IMG- " . $request->hasfile("references")];
|
||||
return ['status' => true, 'message' => "Location Editited" . $request->hasfile("references")];
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Location Not Editited"];
|
||||
}
|
||||
|
145
app/Repositories/MemberRepository.php
Normal file
145
app/Repositories/MemberRepository.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Member;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class MemberRepository
|
||||
{
|
||||
protected $model;
|
||||
|
||||
public function __construct(Member $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
{
|
||||
return $this->model::all();
|
||||
}
|
||||
|
||||
public function get($uuid)
|
||||
{
|
||||
return $this->model::where("uuid", $uuid)->first();
|
||||
}
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
//get member to edit
|
||||
$member = $this->get($request->id);
|
||||
|
||||
//save new avi if available
|
||||
$publicPath = '../public/';
|
||||
$refPath = 'assets/images/members/' . $member->uuid;
|
||||
if ($request->hasfile("fresh_avi")) {
|
||||
$file = $request->fresh_avi;
|
||||
if (!is_dir($publicPath . $refPath)) {
|
||||
mkdir($publicPath . $refPath, 0755, true);
|
||||
}
|
||||
$filename = urlencode($file->getClientOriginalName());
|
||||
$file->move($publicPath . $refPath, $filename);
|
||||
$freshAvi = '/' . $refPath . '/' . $filename;
|
||||
$member->avatar = $freshAvi;
|
||||
}
|
||||
|
||||
$member->handle = $request->handle;
|
||||
$member->email = $request->email;
|
||||
$member->pronoun = $request->pronouns;
|
||||
$member->role = $request->role;
|
||||
$member->active = $request->status;
|
||||
|
||||
if ($member->save()) {
|
||||
return ['status' => true, 'message' => "Member Editited"];
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Member Not Editited"];
|
||||
}
|
||||
}
|
||||
|
||||
public function add($request)
|
||||
{
|
||||
$password = [];
|
||||
$newFriend = [];
|
||||
if ($request->fresh_pass === $request->fresh_pass_confirm) {
|
||||
$password = Hash::make($request->fresh_pass);
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Passwords Do Not Match"];
|
||||
}
|
||||
|
||||
//if role paramter is set, not an admin add
|
||||
if (isset($request->role)) {
|
||||
$newFriend = $this->model::create([
|
||||
'uuid' => Uuid::uuid4(),
|
||||
'avatar' => 'default-member-avatar',
|
||||
'handle' => $request->handle,
|
||||
'email' => $request->email,
|
||||
'pronoun' => $request->pronouns,
|
||||
'role' => $request->role,
|
||||
'active' => $request->status,
|
||||
'password' => $password,
|
||||
'created_at' => Carbon::now(),
|
||||
'last_login' => Carbon::now(),
|
||||
]);
|
||||
} else {
|
||||
//set up admin
|
||||
$newFriend = $this->model::create([
|
||||
'uuid' => Uuid::uuid4(),
|
||||
'avatar' => 'default-member-avatar',
|
||||
'handle' => $request->handle,
|
||||
'email' => $request->email,
|
||||
'pronoun' => $request->pronouns,
|
||||
'role' => 0,
|
||||
'active' => true,
|
||||
'password' => $password,
|
||||
'created_at' => Carbon::now(),
|
||||
'last_login' => Carbon::now(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($newFriend) {
|
||||
return ['status' => true, 'message' => "New Friend Made!"];
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Uh oh, New Friend Delay!"];
|
||||
}
|
||||
}
|
||||
|
||||
public function editProfile($request)
|
||||
{
|
||||
//get member to edit
|
||||
$member = $this->get($request->id);
|
||||
|
||||
//save new avi if available
|
||||
$publicPath = '../public/';
|
||||
$refPath = 'assets/images/members/' . $member->uuid;
|
||||
if ($request->hasfile("fresh_avi")) {
|
||||
$file = $request->fresh_avi;
|
||||
if (!is_dir($publicPath . $refPath)) {
|
||||
mkdir($publicPath . $refPath, 0755, true);
|
||||
}
|
||||
$filename = urlencode($file->getClientOriginalName());
|
||||
$file->move($publicPath . $refPath, $filename);
|
||||
$freshAvi = '/' . $refPath . '/' . $filename;
|
||||
$member->avatar = $freshAvi;
|
||||
}
|
||||
//changing password
|
||||
if (isset($request->fresh_pass) && $request->fresh_pass !== '') {
|
||||
if ($request->fresh_pass === $request->fresh_pass_confirm) {
|
||||
$member->password = Hash::make($request->fresh_pass);
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Passwords Do Not Match"];
|
||||
}
|
||||
}
|
||||
|
||||
$member->handle = $request->handle;
|
||||
$member->email = $request->email;
|
||||
$member->pronoun = $request->pronouns;
|
||||
|
||||
if ($member->save()) {
|
||||
return ['status' => true, 'message' => "Profile Editited"];
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Profile Not Editited"];
|
||||
}
|
||||
}
|
||||
}
|
@ -9,10 +9,26 @@ use GuzzleHttp\Exception\ConnectException;
|
||||
class SourceRepository
|
||||
{
|
||||
protected $source;
|
||||
protected $missing;
|
||||
protected $updated;
|
||||
protected $sources;
|
||||
|
||||
public function __construct(Source $source)
|
||||
{
|
||||
$this->source = $source;
|
||||
$this->source = $source;
|
||||
$this->missing = [];
|
||||
$this->updated = [];
|
||||
$this->sources = $source::where("active", true)->get();
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
{
|
||||
return $this->source::all();
|
||||
}
|
||||
|
||||
public function get($id)
|
||||
{
|
||||
return $this->source::where("id", $id)->first();
|
||||
}
|
||||
|
||||
public function getActive()
|
||||
@ -20,51 +36,120 @@ class SourceRepository
|
||||
return $this->source::where("active", true)->get();
|
||||
}
|
||||
|
||||
public function updateSourceData()
|
||||
public function edit($request)
|
||||
{
|
||||
$source = $this->get($request->id);
|
||||
$source->url = $request->url;
|
||||
$source->type = $request->type;
|
||||
$source->format = $request->format;
|
||||
$source->active = $request->status;
|
||||
$source->last_updated = Carbon::now();
|
||||
//token check
|
||||
if ($request->token != '' && $request->token != 'none') {
|
||||
$source->token = $request->token;
|
||||
} else {
|
||||
$source->token = '';
|
||||
}
|
||||
|
||||
if ($source->save()) {
|
||||
return ['status' => true, 'message' => "Source Editited"];
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Source Not Editited"];
|
||||
}
|
||||
}
|
||||
|
||||
public function add($request)
|
||||
{
|
||||
$newSource = $this->source::create([
|
||||
'url' => $request->url,
|
||||
'type' => $request->type,
|
||||
'format' => $request->format,
|
||||
'active' => $request->status,
|
||||
'token' => $request->token,
|
||||
'last_udated' => Carbon::now(),
|
||||
]);
|
||||
|
||||
if ($newSource) {
|
||||
return ['status' => true, 'message' => "New Source Created!"];
|
||||
} else {
|
||||
return ['status' => false, 'message' => "Uh oh, New Source Not Created!"];
|
||||
}
|
||||
}
|
||||
|
||||
public function updateSourceData($index = 0)
|
||||
{
|
||||
$sources = $this->getActive();
|
||||
$missing = [];
|
||||
$checked = [];
|
||||
//checks all the sources to refresh data
|
||||
foreach ($sources as $source) {
|
||||
$result = [];
|
||||
if ($source['type'] == 'mastodon') {
|
||||
if ($source['token'] == null) {
|
||||
try {
|
||||
$result = \Mastodon::domain('https://' . $source['url'])
|
||||
->get('/instance/domain_blocks');
|
||||
array_push($checked, ['source' => $source->url]);
|
||||
} catch (ConnectException $e) {
|
||||
array_push($missing, ['source' => $source->url]);
|
||||
}
|
||||
$count = count($this->sources);
|
||||
if ($count == 0) {
|
||||
return [
|
||||
'checked' => $this->updated,
|
||||
'notchecked' => $this->missing,
|
||||
'count' => $count,
|
||||
'updated' => 'false',
|
||||
];
|
||||
} else {
|
||||
//check index
|
||||
if ($index <= $count - 1) {
|
||||
$source = $this->sources[$index];
|
||||
$result = $this->getMastoBlocklist($source);
|
||||
if (count($result) > 0) {
|
||||
$source->list_data = json_encode($result);
|
||||
$source->last_updated = Carbon::now();
|
||||
$source->save();
|
||||
array_push($this->updated, ['source' => $source->url]);
|
||||
$index++;
|
||||
$result = $this->updateSourceData($index);
|
||||
} else {
|
||||
try {
|
||||
$result = \Mastodon::domain('https://' . $source['url'])
|
||||
->token($source['token'])
|
||||
->get('/instance/domain_blocks');
|
||||
array_push($checked, ['source' => $source->url]);
|
||||
} catch (ConnectException $e) {
|
||||
}
|
||||
//if empty run the same index again
|
||||
array_push($this->missing, ['source' => $source->url]);
|
||||
$result = $this->updateSourceData($index);
|
||||
}
|
||||
} elseif ($source['type'] == 'custom' && $source['format'] == 'csv') {
|
||||
} else {
|
||||
//continue
|
||||
}
|
||||
return [
|
||||
'checked' => $this->updated,
|
||||
'notchecked' => $this->missing,
|
||||
'count' => $count,
|
||||
'updated' => 'true',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function getMastoBlocklist($source)
|
||||
{
|
||||
$result = [];
|
||||
if ($source['type'] == 'mastodon') {
|
||||
if ($source['token'] == null) {
|
||||
try {
|
||||
$denylist = array_map('str_getcsv', file('https://' . $source['url']));
|
||||
foreach ($denylist as $item) {
|
||||
array_push($result, [
|
||||
'domain' => $item[0],
|
||||
'severity' => $item[1],
|
||||
'comment' => $item[2]]);
|
||||
}
|
||||
array_push($checked, ['source' => $source->url]);
|
||||
} catch (Exception $e) {
|
||||
array_push($missing, ['source' => $source->url]);
|
||||
$result = \Mastodon::domain('https://' . $source['url'])
|
||||
->get('/instance/domain_blocks');
|
||||
} catch (ConnectException $e) {
|
||||
//TODO: Logo Errors
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$result = \Mastodon::domain('https://' . $source['url'])
|
||||
->token($source['token'])
|
||||
->get('/instance/domain_blocks');
|
||||
} catch (ConnectException $e) {
|
||||
//TODO: Logo Errors
|
||||
}
|
||||
}
|
||||
|
||||
$source->list_data = json_encode($result);
|
||||
$source->last_updated = Carbon::now();
|
||||
$source->save();
|
||||
} elseif ($source['type'] == 'custom' && $source['format'] == 'csv') {
|
||||
try {
|
||||
$denylist = array_map('str_getcsv', file('https://' . $source['url']));
|
||||
foreach ($denylist as $item) {
|
||||
array_push($result, [
|
||||
'domain' => $item[0],
|
||||
'severity' => $item[1],
|
||||
'comment' => $item[2]]);
|
||||
}
|
||||
array_push($checked, ['source' => $source->url]);
|
||||
} catch (Exception $e) {
|
||||
array_push($missing, ['source' => $source->url]);
|
||||
}
|
||||
}
|
||||
return ['checked' => $checked, 'notchecked' => $missing];
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,11 @@ class UpdateService
|
||||
public function data()
|
||||
{
|
||||
$response = $this->source->updateSourceData();
|
||||
return count($response['checked']) . ' SOURCES UPDATED - ' .
|
||||
count($response['notchecked']) . ' SOURCES NOT CHECKED';
|
||||
if ($response['updated'] == 'true') {
|
||||
return count($response['checked']) . ' SOURCES UPDATED';
|
||||
} else {
|
||||
return 'NO SOURCES PRESENT';
|
||||
}
|
||||
}
|
||||
|
||||
public function list()
|
||||
@ -33,14 +36,15 @@ class UpdateService
|
||||
$fresh = 0;
|
||||
$unified = [];
|
||||
|
||||
$sources = $this->source->getActive();
|
||||
$sources = $this->source->getActive();
|
||||
$locations = $this->location->getActiveLocations();
|
||||
|
||||
foreach ($sources as $source) {
|
||||
//$listData = json_decode();
|
||||
foreach (json_decode($source->list_data) as $item) {
|
||||
$index = array_search($item->domain, array_column($unified, 'url'));
|
||||
if ($index) {
|
||||
//if there is a match, update the count
|
||||
//if there is a match, update the count and comment
|
||||
if ($item->severity == "suspend" || $item->severity == "defederate") {
|
||||
++$unified[$index]['block_count'];
|
||||
array_push($unified[$index]['block_vote'], $source->url);
|
||||
@ -48,6 +52,9 @@ class UpdateService
|
||||
++$unified[$index]['silence_count'];
|
||||
array_push($unified[$index]['silence_vote'], $source->url);
|
||||
}
|
||||
if (!is_null($item->comment) && $item->comment != ' ' && $item->comment != '') {
|
||||
$unified[$index]['comment'] = $item->comment . '+' . $unified[$index]['comment'];
|
||||
}
|
||||
} else {
|
||||
$silence = 0;
|
||||
$suspend = 0;
|
||||
@ -74,6 +81,12 @@ class UpdateService
|
||||
}
|
||||
}
|
||||
|
||||
//clear out all previous descriptions
|
||||
foreach ($locations as $loc) {
|
||||
$loc->public_comments = ' ';
|
||||
$loc->save();
|
||||
}
|
||||
|
||||
foreach ($unified as $item) {
|
||||
$location = $this->location->getLocation($item['url']);
|
||||
if ($location) {
|
||||
@ -86,6 +99,12 @@ class UpdateService
|
||||
$location->silence_count = $item['silence_count'];
|
||||
$location->silence_vote = [];
|
||||
$location->silence_vote = $item['silence_vote'];
|
||||
//clear descriptions
|
||||
if (!is_null($item['comment']) || !$item['comment'] != " ") {
|
||||
$location->public_comments = $item['comment'];
|
||||
} else {
|
||||
$location->public_comments = 'Comments Pending';
|
||||
}
|
||||
|
||||
$location->actions_count = $item['block_count'] + $item['silence_count'];
|
||||
|
||||
@ -112,20 +131,20 @@ class UpdateService
|
||||
}
|
||||
|
||||
$new = Location::create([
|
||||
'uuid' => Uuid::uuid4(),
|
||||
'name' => $item['url'],
|
||||
'url' => $item['url'],
|
||||
'description' => ($item['comment'] != null) ? $item['comment'] : "no description",
|
||||
'active' => $status,
|
||||
'rating' => $rating,
|
||||
'added_by' => 1,
|
||||
'tags' => 'poor moderation, hate speech',
|
||||
'images' => json_encode($images),
|
||||
'block_count' => $item['block_count'],
|
||||
'block_vote' => $item['block_vote'],
|
||||
'silence_count' => $item['silence_count'],
|
||||
'silence_vote' => $item['silence_vote'],
|
||||
'actions_cont' => $item['block_count'] + $item['silence_count']
|
||||
'uuid' => Uuid::uuid4(),
|
||||
'name' => $item['url'],
|
||||
'url' => $item['url'],
|
||||
'public_comments' => ($item['comment'] != null) ? $item['comment'] : "comments pending",
|
||||
'active' => $status,
|
||||
'rating' => $rating,
|
||||
'added_by' => 1,
|
||||
'tags' => 'poor moderation, hate speech',
|
||||
'images' => json_encode($images),
|
||||
'block_count' => $item['block_count'],
|
||||
'block_vote' => $item['block_vote'],
|
||||
'silence_count' => $item['silence_count'],
|
||||
'silence_vote' => $item['silence_vote'],
|
||||
'actions_cont' => $item['block_count'] + $item['silence_count']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,39 @@
|
||||
{
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"name": "project/thebadspace",
|
||||
"type": "moderation",
|
||||
"description": "A tool for improving independent social media curation",
|
||||
"version": "0.7-alpha",
|
||||
"keywords": [
|
||||
"thebadspace",
|
||||
"tbs",
|
||||
"activty-pub",
|
||||
"laravel",
|
||||
"framework",
|
||||
"moderation",
|
||||
"safety",
|
||||
"curation",
|
||||
"tooling",
|
||||
"fediverse"
|
||||
],
|
||||
"license": [
|
||||
"GPL-3.0-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ro",
|
||||
"homepage": "https://roiskinda.cool"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"source": "https://koodu.h-i.works/projects/thebadspace",
|
||||
"wiki": "https://koodu.h-i.works/projects/thebadspace/wiki/?action=_pages",
|
||||
"issues": "https://koodu.h-i.works/projects/thebadspace/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.2",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"laravel/framework": "^10.10",
|
||||
"laravel/sanctum": "^3.2",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.8",
|
||||
"revolution/laravel-mastodon-api": "^3.0"
|
||||
},
|
||||
@ -17,8 +42,8 @@
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.1",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
@ -46,11 +71,17 @@
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi"
|
||||
]
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"php -r \"copy('.env.example', '.env');\"",
|
||||
"php artisan key:generate"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
"dont-discover": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
|
2787
composer.lock
generated
2787
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
}
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('location', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->uuid('uuid');
|
||||
$table->string('name', length: 255);
|
||||
$table->string('url', length: 255);
|
||||
$table->text('public_comments');
|
||||
$table->json('images')->nullable();
|
||||
$table->boolean('active');
|
||||
$table->string('rating', length: 255);
|
||||
$table->integer('added_by');
|
||||
$table->timestamps(precision: 0);
|
||||
$table->timestamp('deleted_at', precision: 0)->nullable();
|
||||
$table->string('tags', length: 255);
|
||||
$table->integer('block_count')->nullable();
|
||||
$table->integer('silence_count')->nullable();
|
||||
$table->integer('actions_count')->nullable();
|
||||
$table->text('archive_links')->nullable();
|
||||
$table->json('block_vote')->nullable();
|
||||
$table->json('silence_vote')->nullable();
|
||||
$table->text('notes')->nullable();
|
||||
});
|
||||
|
||||
DB::statement("ALTER TABLE location ADD COLUMN searchtext TSVECTOR");
|
||||
DB::statement("UPDATE location SET searchtext = to_tsvector('english', name)");
|
||||
DB::statement("UPDATE location SET searchtext = to_tsvector('english', url)");
|
||||
DB::statement("UPDATE location SET searchtext = to_tsvector('english', public_comments)");
|
||||
DB::statement("UPDATE location SET searchtext = to_tsvector('english', tags)");
|
||||
DB::statement("CREATE INDEX searchtext_gin ON location USING GIN(searchtext)");
|
||||
DB::statement("CREATE TRIGGER ts_searchtext BEFORE INSERT OR UPDATE ON location FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger('searchtext', 'pg_catalog.english', 'name', 'url', 'public_comments', 'tags')");
|
||||
}
|
||||
|
||||
//'name', 'url', 'public_comments', 'tags'
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement("DROP TRIGGER IF EXISTS tsvector_update_trigger ON location");
|
||||
DB::statement("DROP INDEX IF EXISTS searchtext_gin");
|
||||
DB::statement("ALTER TABLE location DROP COLUMN searchtext");
|
||||
Schema::dropIfExists('location');
|
||||
}
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('member', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->uuid('uuid');
|
||||
$table->string('handle', length: 255);
|
||||
$table->string('email', length: 255);
|
||||
$table->string('password', length: 255);
|
||||
$table->string('avatar', length: 255)->nullable();
|
||||
$table->string('pronoun', length: 255);
|
||||
$table->string('gender', length: 255)->nullable();
|
||||
$table->boolean('active');
|
||||
$table->integer('role')->nullable();
|
||||
$table->timestamp('created_at', precision: 0);
|
||||
$table->timestamp('last_login', precision: 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('member');
|
||||
}
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('source', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('url', length: 255);
|
||||
$table->string('type', length: 255);
|
||||
$table->boolean('active');
|
||||
$table->integer('admin_id')->nullable();
|
||||
$table->string('format', length: 255);
|
||||
$table->string('token', length: 255)->nullable();
|
||||
$table->timestamp('last_updated', precision: 0)->nullable();
|
||||
$table->json('list_data')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('source');
|
||||
}
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('appeal', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->uuid('uuid');
|
||||
$table->string('location', length: 255);
|
||||
$table->string('location_admin', length: 255);
|
||||
$table->string('sponsor', length: 255);
|
||||
$table->text('description');
|
||||
$table->boolean('reviewed')->default(false);
|
||||
$table->boolean('approved')->default(false);
|
||||
$table->timestamps(precision: 0);
|
||||
$table->timestamp('deleted_at', precision: 0)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('appeal');
|
||||
}
|
||||
};
|
@ -3,6 +3,12 @@ section.index-search {
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
/* TODO: move to a global file? it’s not only for forms. */
|
||||
section.index-search :focus-visible {
|
||||
outline: 2px solid var(--white);
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
section.start a.search-link {
|
||||
color: var(--highlight);
|
||||
font-size: 2.5em;
|
||||
@ -12,36 +18,57 @@ section.start a.search-link {
|
||||
}
|
||||
|
||||
form.index-search-form {
|
||||
display: grid;
|
||||
grid-template-columns: auto max-content;
|
||||
gap: 15px;
|
||||
width: 80%;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 0;
|
||||
}
|
||||
|
||||
form.index-search-form > input,
|
||||
form.index-search-form > button {
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form.index-search-form > input:focus {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
form.index-search-form > input[type="text"] {
|
||||
width: 88%;
|
||||
height: 50px;
|
||||
font: 44px var(--base-type);
|
||||
}
|
||||
|
||||
form.index-search-form > input[type="hidden"] {
|
||||
/* This removes it from the grid calculations */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
form.index-search-form > button {
|
||||
height: 60px;
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
width: 60px;
|
||||
position: relative;
|
||||
top: 9px;
|
||||
right: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
form.index-search-form > button > img#search-icon {
|
||||
float: none;
|
||||
form.index-search-form > button > svg {
|
||||
justify-self: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
form.index-search-form > button > label {
|
||||
font-weight: 500;
|
||||
top: 15px;
|
||||
position: relative;
|
||||
font-size: 1.5em;
|
||||
form.index-search-form > button > span {
|
||||
display: none;
|
||||
margin-top: 3px;
|
||||
font-weight: 500;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
@ -50,59 +77,65 @@ form.index-search-form > button > label {
|
||||
}
|
||||
|
||||
section.index-meta article {
|
||||
margin-top: 20px;
|
||||
padding-block: 30px;
|
||||
}
|
||||
|
||||
div.index-meta {
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
gap: 10px;
|
||||
width: 98%;
|
||||
font-weight: 500;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
div.index-meta > label:nth-child(2),
|
||||
div.index-meta > label:nth-child(4),
|
||||
div.index-meta > label:nth-child(6) {
|
||||
color: var(--white);
|
||||
table.index-meta {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
table.index-meta tr + tr > * {
|
||||
padding-block-start: 10px;
|
||||
}
|
||||
|
||||
table.index-meta :is(th, td) {
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
table.index-meta th {
|
||||
color: var(--secondary);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.index-meta td {
|
||||
padding-inline-start: 10px;
|
||||
color: var(--white);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.index-search-form > input[type="text"] {
|
||||
width: 85%;
|
||||
height: 50px;
|
||||
font: 34px var(--base-type);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
form.index-search-form > input[type="text"] {
|
||||
width: 80%;
|
||||
height: 50px;
|
||||
font: 34px var(--base-type);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
form.index-search-form {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
form.index-search-form > input[type="text"] {
|
||||
width: 99%;
|
||||
height: 50px;
|
||||
font: 27px var(--base-type);
|
||||
}
|
||||
|
||||
form.index-search-form > input[type="text"],
|
||||
form.index-search-form > button {
|
||||
width: 99%;
|
||||
top: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form.index-search-form > button > label {
|
||||
display: inline;
|
||||
form.index-search-form > button {
|
||||
grid-template-columns: auto 60px;
|
||||
}
|
||||
|
||||
form.index-search-form > button > img#search-icon {
|
||||
float: right;
|
||||
form.index-search-form > button span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ a.list-link > .item-block > .item-icon {
|
||||
a.list-link {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-template-rows: 100% 100px 30px 30px;
|
||||
grid-template-rows: 1fr auto 30px 30px;
|
||||
gap: 5px;
|
||||
height: auto;
|
||||
padding-bottom: 20px;
|
||||
|
@ -1,3 +1,4 @@
|
||||
@import "../global/utilities.css";
|
||||
@import "../global/colors.css";
|
||||
@import "../global/forms.css";
|
||||
@import "../global/typography.css";
|
||||
|
@ -8,16 +8,25 @@ input[type="text"] {
|
||||
display: inline-block;
|
||||
background: var(--white);
|
||||
color: var(--primary);
|
||||
transition: all 0.2s linear;
|
||||
transition: 0.2s linear;
|
||||
transition-property: color, background-color;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: solid var(--highlight);
|
||||
background-color: var(--highlight);
|
||||
}
|
||||
|
||||
/* TODO: generalise this a bit */
|
||||
button:focus,
|
||||
input[type="submit"]:focus,
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: 2px solid var(--white);
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
@ -34,8 +43,10 @@ input[type="submit"] {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
transition: all 0.3s linear;
|
||||
transition: 0.3s linear;
|
||||
transition-property: color, background-color;
|
||||
height: 35px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
select {
|
||||
@ -45,4 +56,5 @@ select {
|
||||
appearance: none;
|
||||
color: var(--primary);
|
||||
background: var(--secondary);
|
||||
height: 35px;
|
||||
}
|
||||
|
@ -199,9 +199,7 @@ footer {
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
height: auto;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
max-width: 1000px;
|
||||
width: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@ -213,6 +211,15 @@ footer > div:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/*
|
||||
member stuff
|
||||
*/
|
||||
|
||||
.your-avatar {
|
||||
width: 250px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
responsive
|
||||
*/
|
||||
|
@ -67,6 +67,11 @@ h3 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h3.strong {
|
||||
color: var(--secondary);
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
|
13
public/assets/css/global/utilities.css
Normal file
13
public/assets/css/global/utilities.css
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
|
||||
.visually-hidden:not(:focus):not(:active) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
BIN
public/assets/images/global/default-avi.png
Normal file
BIN
public/assets/images/global/default-avi.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -1,8 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated by Pixelmator Pro 3.3.8 -->
|
||||
<svg width="101" height="100" viewBox="0 0 101 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group-copy">
|
||||
<path id="Path-copy-2" fill="none" stroke="#efebe3" stroke-width="8.777778" stroke-linecap="round" stroke-linejoin="round" d="M 11 41.722221 C 11 58.68964 24.754808 72.444443 41.722221 72.444443 C 58.68964 72.444443 72.444443 58.68964 72.444443 41.722221 C 72.444443 24.754807 58.68964 11 41.722221 11 C 24.754808 11 11 24.754807 11 41.722221"/>
|
||||
<path id="Path-copy" fill="none" stroke="#efebe3" stroke-width="8.777778" stroke-linecap="round" stroke-linejoin="round" d="M 90 90 L 63.666668 63.666668"/>
|
||||
</g>
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="search">
|
||||
<path fill="none" stroke="currentcolor" stroke-width="8.777778" stroke-linecap="round" stroke-linejoin="round" d="M 11 41.722221 C 11 58.68964 24.754808 72.444443 41.722221 72.444443 C 58.68964 72.444443 72.444443 58.68964 72.444443 41.722221 C 72.444443 24.754807 58.68964 11 41.722221 11 C 24.754808 11 11 24.754807 11 41.722221"/>
|
||||
<path fill="none" stroke="currentcolor" stroke-width="8.777778" stroke-linecap="round" stroke-linejoin="round" d="M 90 90 L 63.666668 63.666668"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 571 B |
@ -11,8 +11,10 @@
|
||||
@csrf
|
||||
<label>Edit Location Name</label><br>
|
||||
<input type="text" name="name" value="{{$location->name}}" /><br>
|
||||
<label>Edit Location Comments</label><br>
|
||||
<textarea name="description">{{$location->description}}</textarea><br>
|
||||
|
||||
<label>Edit Location Notes</label><br>
|
||||
<textarea name="notes">{{$location->notes}}</textarea><br>
|
||||
|
||||
<label>Edit Reference Links (comma seperated)</label><br>
|
||||
<textarea name="archive_links">{{$location->archive_links}}</textarea><br>
|
||||
<label>Edit Reference Images</label><br>
|
||||
@ -24,7 +26,7 @@
|
||||
<h3>Images</h3>
|
||||
@if($images != null)
|
||||
@foreach($images as $image)
|
||||
<a href="/{{$image->path}}" class="location-image" style="background: url(/{{$image->path}}) no-repeat center center / cover #fc6399" />
|
||||
<a href="{{$image->path}}" class="location-image" style="background: url({{$image->path}}) no-repeat center center / cover #fc6399" />
|
||||
</a>
|
||||
@endforeach
|
||||
@endif
|
||||
|
@ -2,12 +2,51 @@
|
||||
|
||||
@section('title', 'Den | Member Admin')
|
||||
|
||||
@section('main-content')
|
||||
<section>
|
||||
<article>
|
||||
<h2>Member Listing </h2>
|
||||
<a href="/den/admin/update">UPDATE LOCATIONS</a><br />
|
||||
<a href="/den/admin/compile">COMPILE LOCATIONS</a>
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
||||
@php
|
||||
switch($mode)
|
||||
{
|
||||
case 'member-create':
|
||||
$action_url = '/den/member/create';
|
||||
break;
|
||||
case 'member-edit':
|
||||
$action_url = '/den/member/edit';
|
||||
break;
|
||||
case 'admin-create':
|
||||
$action_url = '/den/member/admin-create';
|
||||
break;
|
||||
}
|
||||
@endphp
|
||||
@section('main-content')
|
||||
<section>
|
||||
<article>
|
||||
@switch($mode)
|
||||
@case('member-edit')
|
||||
<h2>Edit Info for {{$member->handle}}</h2>
|
||||
@include('forms.member-edit')
|
||||
<br />
|
||||
@break
|
||||
|
||||
@case('member-create')
|
||||
<h2>New Member Info</h2>
|
||||
@include('forms.member-edit')
|
||||
<br />
|
||||
@break
|
||||
|
||||
@case('admin-create')
|
||||
<h2>Make your first account</h2>
|
||||
*This will be your administrator account.
|
||||
@include('forms.member-edit')
|
||||
<br />
|
||||
@break
|
||||
|
||||
@default
|
||||
<h2>Member Listing </h2>
|
||||
@foreach($members as $member)
|
||||
<a href="/den/member/{{$member->uuid}}">{{$member->handle}}</a><br />
|
||||
@endforeach
|
||||
<h2>Add Member </h2>
|
||||
<a href="/den/member/edit/create">Make a new friend</a><br />
|
||||
@endswitch
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
13
resources/views/back/profile.blade.php
Normal file
13
resources/views/back/profile.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
@extends('frame')
|
||||
|
||||
@section('title', 'Den | Your Profile')
|
||||
|
||||
@section('main-content')
|
||||
<section>
|
||||
<article>
|
||||
<h2>Edit Profile Deets </h2>
|
||||
@include('forms.profile-edit')
|
||||
</article>
|
||||
</section>
|
||||
<br />
|
||||
@endsection
|
42
resources/views/back/sources.blade.php
Normal file
42
resources/views/back/sources.blade.php
Normal file
@ -0,0 +1,42 @@
|
||||
@extends('frame')
|
||||
|
||||
@section('title', 'Den | Sources Admin')
|
||||
|
||||
@php
|
||||
switch($mode)
|
||||
{
|
||||
case 'source-create':
|
||||
$action_url = '/den/source/create';
|
||||
break;
|
||||
case 'source-edit':
|
||||
$action_url = '/den/source/edit';
|
||||
break;
|
||||
}
|
||||
@endphp
|
||||
@section('main-content')
|
||||
<section>
|
||||
<article>
|
||||
@switch($mode)
|
||||
@case('source-edit')
|
||||
<h2>Edit Info for {{$source->url}}</h2>
|
||||
@include('forms.source-edit')
|
||||
<br />
|
||||
@break
|
||||
|
||||
@case('source-create')
|
||||
<h2>New Source Info</h2>
|
||||
@include('forms.source-edit')
|
||||
<br />
|
||||
@break
|
||||
|
||||
@default
|
||||
<h2>Current Sources </h2>
|
||||
@foreach($sources as $source)
|
||||
<a href="/den/source/{{$source->id}}">{{$source->url}}</a><br />
|
||||
@endforeach
|
||||
<h2>Add Source </h2>
|
||||
<a href="/den/source/edit/create">Add a new Source</a><br />
|
||||
@endswitch
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
@ -2,16 +2,17 @@
|
||||
|
||||
@section('title', 'Den | Start')
|
||||
|
||||
@section('main-content')
|
||||
@section('main-content')
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<h2>Hey {{$handle}} </h2>
|
||||
<a href="/den/you">Edit Your Account</a><br />
|
||||
<a href="/den/locations">Manage Locations</a><br />
|
||||
@if($role==1)
|
||||
<a href="/den/member">Manage Members</a><br />
|
||||
@endif
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
||||
<section>
|
||||
<article>
|
||||
<h2>Hey {{$handle}} </h2>
|
||||
<a href="/den/you">Edit Your Account</a><br />
|
||||
@if($role==0)
|
||||
<a href="/den/locations">Manage Locations</a><br />
|
||||
<a href="/den/member">Manage Members</a><br />
|
||||
<a href="/den/sources">Manage Sources</a><br />
|
||||
@endif
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
74
resources/views/forms/member-edit.blade.php
Normal file
74
resources/views/forms/member-edit.blade.php
Normal file
@ -0,0 +1,74 @@
|
||||
<form action="{{$action_url}}" method="post" enctype="multipart/form-data">
|
||||
<div>
|
||||
@php
|
||||
isset($avatar) ? $avi = $avatar : $avi = '';
|
||||
@endphp
|
||||
<img class="your-avatar" src='{{$avi}}'>
|
||||
<br />
|
||||
<label>Handle</label><br />
|
||||
@php
|
||||
isset($member->handle) ? $handle = $member->handle : $handle = '';
|
||||
@endphp
|
||||
<input type="text" name="handle" value="{{$handle}}" />
|
||||
<br />
|
||||
@php
|
||||
isset($member->email) ? $email = $member->email : $email = '';
|
||||
@endphp
|
||||
<label>Email</label><br />
|
||||
<input type="text" name="email" value="{{$email}}" />
|
||||
<br />
|
||||
@php
|
||||
isset($member->pronoun) ? $pronoun = $member->pronoun : $pronoun = '';
|
||||
@endphp
|
||||
<label>Pronouns</label><br />
|
||||
<input type="text" name="pronouns" value="{{$pronoun}}" />
|
||||
<br />
|
||||
@php
|
||||
isset($member->role) ? $role = $member->role : $role = 2;
|
||||
//for creation of initial admin account
|
||||
if($mode == 'admin-create')
|
||||
{
|
||||
$role = 0;
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if($mode != 'admin-create')
|
||||
<label>Role</label><br />
|
||||
<input type="text" name="role" value="{{$role}}" />
|
||||
<br />
|
||||
@endif
|
||||
|
||||
@if($mode == 'member-create' || $mode == 'admin-create')
|
||||
<label>Fresh Password</label><br />
|
||||
<input type="password" id="fresh_pass" name="fresh_pass" value="" />
|
||||
<br />
|
||||
<label>Confirm Fresh Password</label><br />
|
||||
<input type="password" id="fresh_pass_confirm" name="fresh_pass_confirm" value="" />
|
||||
<br />
|
||||
@endif
|
||||
@php
|
||||
isset($member->active) ? $status = $member->active : $status = false;
|
||||
@endphp
|
||||
|
||||
@if($mode != 'admin-create')
|
||||
<label>Status</label><br />
|
||||
<select name="status">
|
||||
@if($status)
|
||||
<option value="true" selected>Active</option>
|
||||
<option value="false">Not Active</option>
|
||||
@else
|
||||
<option value="true">Active</option>
|
||||
<option value="false" selected>Not Active</option>
|
||||
@endif
|
||||
</select>
|
||||
<br />
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@csrf
|
||||
@php
|
||||
isset($member->uuid) ? $uuid = $member->uuid : $uuid = 0;
|
||||
@endphp
|
||||
<input type="hidden" name="id" value="{{$uuid}}" />
|
||||
<input type="submit" value="Edit Member" name="submit_button">
|
||||
</form>
|
30
resources/views/forms/profile-edit.blade.php
Normal file
30
resources/views/forms/profile-edit.blade.php
Normal file
@ -0,0 +1,30 @@
|
||||
<form action="/den/profile/edit" method="post" enctype="multipart/form-data">
|
||||
<div>
|
||||
<img class="your-avatar" src='{{$avatar}}'>
|
||||
<br />
|
||||
<label>New Avatar</label><br />
|
||||
<input type="file" id="fresh_avi" name="fresh_avi" />
|
||||
<br />
|
||||
<label>Handle</label><br />
|
||||
<input type="text" name="handle" value="{{$handle}}" />
|
||||
<br />
|
||||
<label>Email</label><br />
|
||||
<input type="text" name="email" value="{{$email}}" />
|
||||
<br />
|
||||
<label>Pronouns</label><br />
|
||||
<input type="text" name="pronouns" value="{{$pronouns}}" />
|
||||
<br />
|
||||
<h2>Change Password</h2>
|
||||
<label>Fresh Password</label><br />
|
||||
<input type="password" id="fresh_pass" name="fresh_pass" value="" />
|
||||
<br />
|
||||
<label>Confirm Fresh Password</label><br />
|
||||
<input type="password" id="fresh_pass_confirm" name="fresh_pass_confirm" value="" />
|
||||
<br />
|
||||
|
||||
|
||||
</div>
|
||||
@csrf
|
||||
<input type="hidden" name="id" value="{{$uuid}}" />
|
||||
<input type="submit" value="Edit Profile" name="submit_button">
|
||||
</form>
|
66
resources/views/forms/source-edit.blade.php
Normal file
66
resources/views/forms/source-edit.blade.php
Normal file
@ -0,0 +1,66 @@
|
||||
<form action="{{$action_url}}" method="post" enctype="multipart/form-data">
|
||||
<div>
|
||||
@php
|
||||
isset($source->url) ? $url = $source->url : $url = '';
|
||||
@endphp
|
||||
<label>URL</label><br />
|
||||
<input type="text" name="url" value="{{$url}}" />
|
||||
<br />
|
||||
@php
|
||||
isset($source->type) ? $type = $source->type : $type = '';
|
||||
@endphp
|
||||
<label>Type</label><br />
|
||||
<select name="type">
|
||||
@if($type == 'mastodon')
|
||||
<option value="mastodon" selected>Mastodon</option>
|
||||
<option value="gotosocial">GoToSocial</option>
|
||||
@else
|
||||
<option value="mastodon">Mastodon</option>
|
||||
<option value="gotosocial" selected>GoToSocial</option>
|
||||
@endif
|
||||
</select>
|
||||
<br />
|
||||
@php
|
||||
isset($source->format) ? $format = $source->format : $format = '';
|
||||
@endphp
|
||||
<label>Format</label><br />
|
||||
<select name="format">
|
||||
@if($format == 'json')
|
||||
<option value="json" selected>JSON</option>
|
||||
<option value="csv">CSV</option>
|
||||
@else
|
||||
<option value="json">JSON</option>
|
||||
<option value="csv" selected>CSV</option>
|
||||
@endif
|
||||
</select>
|
||||
<br />
|
||||
|
||||
@php
|
||||
isset($source->active) ? $status = $source->active : $status = false;
|
||||
@endphp
|
||||
<label>Active?</label><br />
|
||||
<select name="status">
|
||||
@if($status)
|
||||
<option value="true" selected>Active</option>
|
||||
<option value="false">Not Active</option>
|
||||
@else
|
||||
<option value="true">Active</option>
|
||||
<option value="false" selected>Not Active</option>
|
||||
@endif
|
||||
</select>
|
||||
<br />
|
||||
@php
|
||||
isset($source->token) ? $token = $source->token : $token = '';
|
||||
@endphp
|
||||
<label>Access Token (enter 'none' to clear)</label><br />
|
||||
<input type="text" name="token" value="{{$token}}" />
|
||||
<br />
|
||||
|
||||
</div>
|
||||
@csrf
|
||||
@php
|
||||
isset($source->id) ? $id = $source->id : $id = 0;
|
||||
@endphp
|
||||
<input type="hidden" name="id" value="{{$id}}" />
|
||||
<input type="submit" value="Edit Source" name="submit_button">
|
||||
</form>
|
@ -2,100 +2,101 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#d66365" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>
|
||||
@yield('title')
|
||||
</title>
|
||||
@if(isset($front) && $front == false)
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/back/start.css?=sdfsdf">
|
||||
@elseif(!isset($front) || $front == true)
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/front/start.css?=sdfsdf">
|
||||
@endif
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#c3639e" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>
|
||||
@yield('title')
|
||||
</title>
|
||||
@if(isset($front) && $front == false)
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/back/start.css?=sdfsdf">
|
||||
@elseif(!isset($front) || $front == true)
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/front/start.css?=sdfsdf">
|
||||
@endif
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<div class="header-left">
|
||||
<a href="/">
|
||||
<img src="/assets/images/global/logo-dark.svg" title="bad-space-logo" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<h1>{{$title}}</h1>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<label for="element-toggle">
|
||||
<img class="menu-icon" src="/assets/images/global/menu.svg" title="menu-open-toggle" />
|
||||
</label>
|
||||
<input id="element-toggle" type="checkbox" />
|
||||
<div id="main-nav">
|
||||
<nav>
|
||||
<label for="element-toggle">
|
||||
<img class="menu-icon" src="/assets/images/global/close.svg" title="menu-open-toggle" />
|
||||
</label><br>
|
||||
<a href="/" title="front" class="nav-links">
|
||||
Front
|
||||
</a><br />
|
||||
<a href="/about" title="about" class="nav-links">
|
||||
About
|
||||
</a><br />
|
||||
<a href="/listings/1" title="instance listing" class="nav-links">
|
||||
Listings
|
||||
</a><br />
|
||||
<a href="/exports" title="list exports" class="nav-links">
|
||||
Exports
|
||||
</a><br />
|
||||
<a href="/appeals" title="location appeals" class="nav-links">
|
||||
Appeals
|
||||
</a><br />
|
||||
@if(Auth::check())
|
||||
<a href="/den" title="den-start" class="nav-links">
|
||||
Den
|
||||
</a><br />
|
||||
<a href="/logout" title="logout" class="nav-links">
|
||||
Logout
|
||||
</a><br />
|
||||
@else
|
||||
<a href="/den" title="login" class="nav-links">
|
||||
The Den
|
||||
</a><br />
|
||||
@endif
|
||||
</nav>
|
||||
<script>
|
||||
0
|
||||
</script>
|
||||
<header>
|
||||
<div>
|
||||
<div class="header-left">
|
||||
<a href="/">
|
||||
<img src="/assets/images/global/logo-dark.svg" alt="The Bad Space" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<h1>{{$title}}</h1>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<label for="element-toggle">
|
||||
<img class="menu-icon" src="/assets/images/global/menu.svg" title="menu-open-toggle" />
|
||||
</label>
|
||||
<input id="element-toggle" type="checkbox" />
|
||||
<div id="main-nav">
|
||||
<nav>
|
||||
<label for="element-toggle">
|
||||
<img class="menu-icon" src="/assets/images/global/close.svg" title="menu-open-toggle" />
|
||||
</label><br>
|
||||
<a href="/" title="front" class="nav-links">
|
||||
Front
|
||||
</a><br />
|
||||
<a href="/about" title="about" class="nav-links">
|
||||
About
|
||||
</a><br />
|
||||
<a href="/listings/1" title="instance listing" class="nav-links">
|
||||
Listings
|
||||
</a><br />
|
||||
<a href="/exports" title="list exports" class="nav-links">
|
||||
Exports
|
||||
</a><br />
|
||||
<a href="/appeals" title="location appeals" class="nav-links">
|
||||
Appeals
|
||||
</a><br />
|
||||
@if(Auth::check())
|
||||
<a href="/den" title="den-start" class="nav-links">
|
||||
Den
|
||||
</a><br />
|
||||
<a href="/logout" title="logout" class="nav-links">
|
||||
Logout
|
||||
</a><br />
|
||||
@else
|
||||
<a href="/den" title="login" class="nav-links">
|
||||
The Den
|
||||
</a><br />
|
||||
@endif
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="system-notice-error" role="status">
|
||||
{{$errors->first()}}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('message'))
|
||||
<div class="system-notice-message" role="status">
|
||||
{!! session('message') !!}
|
||||
</div>
|
||||
@endif
|
||||
@if($errors->any())
|
||||
<div class="system-notice-error" role="status">
|
||||
{{$errors->first()}}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('message'))
|
||||
<div class="system-notice-message" role="status">
|
||||
{!! session('message') !!}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<main>
|
||||
@section('main-content')
|
||||
@show
|
||||
</main>
|
||||
<footer>
|
||||
<div>
|
||||
The Bad Space © 2024<br />
|
||||
an <a href="https://h-i.works">h.i.</a> project
|
||||
</div>
|
||||
<div>
|
||||
a0.6
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
<main>
|
||||
@section('main-content')
|
||||
@show
|
||||
</main>
|
||||
<footer>
|
||||
<div>
|
||||
The Bad Space © <?php echo date("Y"); ?><br />
|
||||
an <a href="https://h-i.works">h.i.</a> project
|
||||
</div>
|
||||
<div>
|
||||
a0.7
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,89 +1,81 @@
|
||||
@extends('frame')
|
||||
@section('title', 'The Bad Space|About')
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article>
|
||||
<h2 id="what">What is The Bad Space?</h2>
|
||||
<p>The Bad Space arose from a need to identify instances that house bad actors, are poorly moderated, and/or contain inappropriate/offensive content (CSAM, hate speech, fascist ideology, etc.) that puts marginalized communities at risk.
|
||||
</p>
|
||||
<p>
|
||||
It is an extension of the
|
||||
<strong>#fediblock</strong>
|
||||
hashtag - orginally created by
|
||||
<a href="https://www.artistmarciax.com/">Artist Marcia X</a>
|
||||
with additional support from
|
||||
<a href="https://digital.rooting.garden">Ginger</a>
|
||||
to provide a catalog of instances that seek to cause harm and reduce the quality of experience in the fediverse.
|
||||
</p>
|
||||
<p>
|
||||
The searchable online catalog is built and maintained by
|
||||
<a href="https://roiskinda.cool/profile.html">Ro</a>. The repo can be found <a href="https://koodu.h-i.works/projects/thebadspace">here</a>.
|
||||
</p>
|
||||
<p>Custom silence and suspend icons graciously provided by <a href="https://rage.love/@puf">puf</a>.</p>
|
||||
<h2 id="how">How does it work?</h2>
|
||||
<p>The Bad Space is a collaboration of communities, referred to as Current Sources, committed to actively moderating against racism, sexism, heterosexism, transphobia, ableism, casteism, or religion.</p>
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article>
|
||||
<h2 id="what">What is The Bad Space?</h2>
|
||||
<p>The Bad Space arose from a need to identify instances that house bad actors, are poorly moderated, and/or contain inappropriate/offensive content (CSAM, hate speech, fascist ideology, etc.) that puts marginalized communities at risk.</p>
|
||||
<p>It is an extension of the <strong>#fediblock</strong> hashtag - orginally created by <a href="https://www.artistmarciax.com/">Artist Marcia X</a> with additional support from <a href="https://digital.rooting.garden">Ginger</a> to provide a catalog of instances that seek to cause harm and reduce the quality of experience in the fediverse.</p>
|
||||
<p>The searchable online catalog is built and maintained by <a href="https://roiskinda.cool/profile.html">Ro</a>. The repo can be found <a href="https://koodu.h-i.works/projects/thebadspace">here</a>.</p>
|
||||
<p>Custom silence and suspend icons graciously provided by <a href="https://rage.love/@puf">puf</a>.</p>
|
||||
|
||||
<p>These communities have permitted The Bad Space to ingest their respective blocklists detailing their silences and suspension to create a composite directory of sites that engage in the behavior(s) listed in the section above. For each behavior, the directory of locations can be searched and, through The Bad Space's public API, integrated into external services.</p>
|
||||
<h2 id="how">How does it work?</h2>
|
||||
<p>The Bad Space is a collaboration of communities, referred to as Current Sources, committed to actively moderating against racism, sexism, heterosexism, transphobia, ableism, casteism, or religion.</p>
|
||||
<p>These communities have permitted The Bad Space to ingest their respective blocklists detailing their silences and suspension to create a composite directory of sites that engage in the behavior(s) listed in the section above. For each behavior, the directory of locations can be searched and, through The Bad Space's public API, integrated into external services.</p>
|
||||
|
||||
<h2>Adding Locations</h2>
|
||||
<p>
|
||||
Current Sources continually review the #fediblock hashtag and update their silences and suspensions when warranted. If an instance meets the criteria of a Current Source to be suspended or silenced, The Bad Space will automatically be updated according to said Current Sources' curated data.
|
||||
<h2>Adding Locations</h2>
|
||||
<p>Current Sources continually review the #fediblock hashtag and update their silences and suspensions when warranted. If an instance meets the criteria of a Current Source to be suspended or silenced, The Bad Space will automatically be updated according to said Current Sources' curated data.</p>
|
||||
<p>For an instance to be listed on The Bad Space, at least two (2) Current Sources must have that location silenced and/or suspended. Instances will not display in the directory until two (2) Current Sources have taken moderation action against them.</p>
|
||||
|
||||
For an instance to be listed on The Bad Space, at least two (2) Current Sources must have that location silenced and/or suspended. Instances will not display in the directory until two (2) Current Sources have taken moderation action against them.
|
||||
</p>
|
||||
<h2>Removing Locations</h2>
|
||||
Locations that are displayed in The Bad Space may petition to be removed from the catalog by sending an appeal request to The Bad Space. The appeal process is outlined <a href="/appeals">here</a>.
|
||||
<h2>Removing Locations</h2>
|
||||
<p>Locations that are displayed in The Bad Space may petition to be removed from the catalog by sending an appeal request to The Bad Space. The appeal process is outlined <a href="/appeals">here</a>.</p>
|
||||
|
||||
<p><strong>Current Sources:</strong></p>
|
||||
Maston:<br />
|
||||
@foreach($sources as $source)
|
||||
@if($source->format == 'json')
|
||||
<a href="https://{{$source->url}}">{{$source->url}}</a><br />
|
||||
@endif
|
||||
@endforeach
|
||||
Custom CSV<br />
|
||||
@foreach($sources as $source)
|
||||
@if($source->format == 'csv')
|
||||
<a href="{{$source->url}}">{{$source->url}}</a><br />
|
||||
@endif
|
||||
@endforeach
|
||||
<h3 class="strong">Current Sources:</h3>
|
||||
|
||||
<h2>How do I use it?</h2>
|
||||
<p>
|
||||
The Bad Space is meant to be a resource for anyone looking to improve the quality of their online experience by creating a tool that catalogs sources for harassment and abuse. There are several options for how it can be used.
|
||||
<h3>Search</h3>
|
||||
To see if a site is listed in the database, use the
|
||||
<a href="/">search feature</a>
|
||||
to search for that URL. If it is in the database, information for that instance will be returned and associated instances if applicable.
|
||||
<h3>CSV Exports</h3>
|
||||
For a list of the current locations being tracked, click on one of the links below to download a dynamically generated CSV file that can be consumed as a blocklist. More formats will be added over time.
|
||||
<br />
|
||||
<a href="/exports/mastodon">For Mastodon</a>
|
||||
<h3>API</h3>
|
||||
The Bad Space has a public api that can be used to search the database programatically and return results in the JSON format. The API can be accsess at<br />
|
||||
<code>https://thebad.space/api/v1/search</code>
|
||||
by posting a JSON object with the following format:
|
||||
<code>{"url":"search.url"}</code><br />
|
||||
Data from API request will be returned in the follow format:<br />
|
||||
<h4>Maston:</h4>
|
||||
<ul>
|
||||
@foreach($sources as $source)
|
||||
@if($source->format == 'json')
|
||||
<li><a href="https://{{$source->url}}">{{$source->url}}</a></li>
|
||||
@else
|
||||
<li>None</li>
|
||||
@endif
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
<pre>
|
||||
<code>{
|
||||
data:{
|
||||
"listingCount":1,
|
||||
"locations":
|
||||
[
|
||||
{
|
||||
"url":"search.url",
|
||||
"name":"Instance Name",
|
||||
"description":"instance description",
|
||||
"link":"bad-space-instance-link"
|
||||
}
|
||||
]
|
||||
<h4>Custom CSV</h4>
|
||||
<ul>
|
||||
@foreach($sources as $source)
|
||||
@if($source->format == 'csv')
|
||||
<li><a href="{{$source->url}}">{{$source->url}}</a></li>
|
||||
@else
|
||||
<!--
|
||||
<li>None</li>
|
||||
-->
|
||||
@endif
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
<h2>How do I use it?</h2>
|
||||
<p>The Bad Space is meant to be a resource for anyone looking to improve the quality of their online experience by creating a tool that catalogs sources for harassment and abuse. There are several options for how it can be used.</p>
|
||||
|
||||
<h3>Search</h3>
|
||||
<p>To see if a site is listed in the database, use the <a href="/">search feature</a> to search for that URL. If it is in the database, information for that instance will be returned and associated instances if applicable.</p>
|
||||
|
||||
<h3>CSV Exports</h3>
|
||||
<p>For a list of the current locations being tracked, click on one of the links below to download a dynamically generated CSV file that can be consumed as a blocklist. More formats will be added over time.</p>
|
||||
<p><a href="/exports">Exports</a></p>
|
||||
|
||||
<h3>API</h3>
|
||||
<p>The Bad Space has a public api that can be used to search the database programatically and return results in the JSON format. The API can be accsess at <code>https://thebad.space/api/v1/search</code> by posting a JSON object with the following format: <code>{"url":"search.url"}</code>. Data from API request will be returned in the follow format:</p>
|
||||
|
||||
<pre>
|
||||
<code>{
|
||||
data:{
|
||||
"listingCount":1,
|
||||
"locations":
|
||||
[
|
||||
{
|
||||
"url":"search.url",
|
||||
"name":"Instance Name",
|
||||
"description":"instance description",
|
||||
"link":"bad-space-instance-link"
|
||||
}
|
||||
}</code>
|
||||
</pre>
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
||||
]
|
||||
}
|
||||
}</code>
|
||||
</pre>
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
@ -1,62 +1,62 @@
|
||||
@extends('frame')
|
||||
@section('title', 'The Bad Space|Appeals')
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article>
|
||||
<h2>Appeals</h2>
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article>
|
||||
<h2>Appeals</h2>
|
||||
|
||||
Locations listed in The Bad Space have the right to appeal their inclusion if they feel they have been added unfairly.
|
||||
Locations listed in The Bad Space have the right to appeal their inclusion if they feel they have been added unfairly.
|
||||
|
||||
<h3>Starting the Appeals Process</h3>
|
||||
<h3>Starting the Appeals Process</h3>
|
||||
|
||||
<p>This process can be initiated by sending an Official Appeal to The Bad Space using the Appeal Form (found below) stating why they should not be included. All appeal requests must be sponsored by at least one Current Source, and their name(s) must be included in the submission.</p>
|
||||
<p>This process can be initiated by sending an Official Appeal to The Bad Space using the Appeal Form (found below) stating why they should not be included. All appeal requests must be sponsored by at least one Current Source, and their name(s) must be included in the submission.</p>
|
||||
|
||||
<p>The Bad Space will validate the appeal request by contacting a moderator or administrator of the respective instance. If no moderator or administrator is detailed in the appeal request, it will be immediately disqualified.</p>
|
||||
<p>The Bad Space will validate the appeal request by contacting a moderator or administrator of the respective instance. If no moderator or administrator is detailed in the appeal request, it will be immediately disqualified.</p>
|
||||
|
||||
<p>If the Appeal contains any threats, hate speech, microaggressions, taunts, or slurs, the Appeal will be automatically disqualified.</p>
|
||||
<p>If the Appeal contains any threats, hate speech, microaggressions, taunts, or slurs, the Appeal will be automatically disqualified.</p>
|
||||
|
||||
An Appeal can only be made once every three (3) months.
|
||||
An Appeal can only be made once every three (3) months.
|
||||
|
||||
<h3>Process Description</h3>
|
||||
<h3>Process Description</h3>
|
||||
|
||||
<p>After an appeal request is validated, it will be reviewed by all Current Sources. Each Current Source will vote on whether to approve the requesting instance's appeal request.</p>
|
||||
<p>After an appeal request is validated, it will be reviewed by all Current Sources. Each Current Source will vote on whether to approve the requesting instance's appeal request.</p>
|
||||
|
||||
<p>80% of Current Sources must vote in favor of the request to proceed to the next step. Appeals that do not meet this threshold will be rejected, and the administrator or moderator who petitioned for the appeal will be notified.
|
||||
</p>
|
||||
<p>80% of Current Sources must vote in favor of the request to proceed to the next step. Appeals that do not meet this threshold will be rejected, and the administrator or moderator who petitioned for the appeal will be notified.
|
||||
</p>
|
||||
|
||||
<p>If an appeal is approved to proceed, a chat room will be created on the h.i. cloud community for the petitioner to be interviewed by Current Sources. Here, they will explain why they feel their instance should be removed from the database.</p>
|
||||
<p>If an appeal is approved to proceed, a chat room will be created on the h.i. cloud community for the petitioner to be interviewed by Current Sources. Here, they will explain why they feel their instance should be removed from the database.</p>
|
||||
|
||||
The petitioner must include the following:
|
||||
<ul>
|
||||
<li>Verification of problematic content and members have been removed.</li>
|
||||
<li>A public explanation of steps to recognize and remove problematic content and members.</li>
|
||||
<li>A working plan to keep said location as free as possible from problematic members and content finding a home there.</li>
|
||||
</ul>
|
||||
The petitioner must include the following:
|
||||
<ul>
|
||||
<li>Verification of problematic content and members have been removed.</li>
|
||||
<li>A public explanation of steps to recognize and remove problematic content and members.</li>
|
||||
<li>A working plan to keep said location as free as possible from problematic members and content finding a home there.</li>
|
||||
</ul>
|
||||
|
||||
<p>The petitioner also must be available to answer any questions from a member of Current Sources concerning the information provided.</p>
|
||||
<p>The petitioner also must be available to answer any questions from a member of Current Sources concerning the information provided.</p>
|
||||
|
||||
<p>Failure to provide any of this information or refusing to be interviewed by Current Sources will result in an automatic disqualification.</p>
|
||||
<p>Failure to provide any of this information or refusing to be interviewed by Current Sources will result in an automatic disqualification.</p>
|
||||
|
||||
<p>Upon completing the interview process, each Current Source will choose to remove or maintain their current block or silence action. The result of each choice will be reflected in the database itself, which can be publicly reviewed at any time.</p>
|
||||
<p>Upon completing the interview process, each Current Source will choose to remove or maintain their current block or silence action. The result of each choice will be reflected in the database itself, which can be publicly reviewed at any time.</p>
|
||||
|
||||
<h2>Appeals Form</h2>
|
||||
<h2>Appeals Form</h2>
|
||||
|
||||
<form action="/appeal" method="post" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<label>Appeal Location</label><br />
|
||||
<input type="text" name="location" value="" /><br />
|
||||
<label>Appeal Location Admin</label><br />
|
||||
<input type="text" name="location_admin" value="" /><br />
|
||||
<label>Appeal Sponsor</label><br />
|
||||
<input type="text" name="sponsor" value="" /><br />
|
||||
<label>What is 1+1?</label><br />
|
||||
<input type="text" name="question" value="" /><br />
|
||||
<label>Appeal Summary</label><br />
|
||||
<textarea name="appeal_description">Appeal Summary</textarea>
|
||||
<input type="hidden" name="h1" value="" /><br />
|
||||
<input type="submit" value="File Appeal" name="submit_button">
|
||||
</form>
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
||||
<form action="/appeal" method="post" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<label>Appeal Location</label><br />
|
||||
<input type="text" name="location" value="" /><br />
|
||||
<label>Appeal Location Admin</label><br />
|
||||
<input type="text" name="location_admin" value="" /><br />
|
||||
<label>Appeal Sponsor</label><br />
|
||||
<input type="text" name="sponsor" value="" /><br />
|
||||
<label>What is 1+1?</label><br />
|
||||
<input type="text" name="question" value="" /><br />
|
||||
<label>Appeal Summary</label><br />
|
||||
<textarea name="appeal_description">Appeal Summary</textarea>
|
||||
<input type="hidden" name="h1" value="" /><br />
|
||||
<input type="submit" value="File Appeal" name="submit_button">
|
||||
</form>
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
@ -1,23 +1,23 @@
|
||||
@extends('frame')
|
||||
@section('title', 'The Bad Space|Exports')
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article>
|
||||
<h2>CSV Exports</h2>
|
||||
Heat Rating is the percentage of Current Sources that have taken action against an instance. The higher the number of Sources that have silenced and/or suspended an instance, the higher the Heat Rating.*
|
||||
<h3>For Mastodon</h3>
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article>
|
||||
<h2>CSV Exports</h2>
|
||||
Heat Rating is the percentage of Current Sources that have taken action against an instance. The higher the number of Sources that have silenced and/or suspended an instance, the higher the Heat Rating.*
|
||||
<h3>For Mastodon</h3>
|
||||
|
||||
@foreach($list as $item)
|
||||
@foreach($list as $item)
|
||||
|
||||
<a href="/exports/mastodon/{{$item['heatRating']}}">Heat Rating: {{$item['heatRating']}}% - Location Count: {{$item['ratingCount']}}</a><br />
|
||||
<a href="/exports/mastodon/{{$item['heatRating']}}">Heat Rating: {{$item['heatRating']}}% - Location Count: {{$item['ratingCount']}}</a><br />
|
||||
|
||||
@endforeach
|
||||
<br />
|
||||
<i>* Heating Ratings are still a work in progress so please review list before using.</i>
|
||||
<br /><br />
|
||||
@endforeach
|
||||
<br />
|
||||
<i>* Heating Ratings are still a work in progress so please review list before using.</i>
|
||||
<br /><br />
|
||||
|
||||
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
@ -5,9 +5,11 @@
|
||||
<section class="index-search">
|
||||
<form class="index-search-form" action="/search" method="post" enctype="multipart/form-data">
|
||||
<input type="text" name="index_search" value="" placeholder="Hi! This is where you search." />
|
||||
<button aria-label="search-button">
|
||||
<label id="search-label">LOOK FOR IT</label>
|
||||
<img id="search-icon" class="button-icon" src="assets/images/global/icon-search.svg" />
|
||||
<button aria-labelledby="search-label">
|
||||
<span id="search-label">Look for it</span>
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="trye">
|
||||
<use href="assets/images/global/icon-search.svg#search" />
|
||||
</svg>
|
||||
</button>
|
||||
@csrf
|
||||
</form>
|
||||
@ -38,33 +40,21 @@
|
||||
@endisset
|
||||
<section class="index-meta">
|
||||
<article>
|
||||
<h2>Recent Updates</h2>
|
||||
@foreach($recent as $item)
|
||||
<a class="list-link" role="listitem" href="/location/{{$item->uuid}}">
|
||||
@php
|
||||
$rating = floor(($item->actions_count / $sources)*100);
|
||||
@endphp
|
||||
<span class="item-rating">{{$rating}}%</span>
|
||||
<label class="item-name">{{$item->name}}</label>
|
||||
<div class="item-silence">
|
||||
<img class="item-icon" src="/assets/images/global/status-silence.svg" title="silenced" />
|
||||
{{$item->silence_count}}
|
||||
</div>
|
||||
<div class="item-block">
|
||||
<img class="item-icon" src="/assets/images/global/status-suspend.svg" title="suspended" />
|
||||
{{$item->block_count}}
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
<h2>Info</h2>
|
||||
<div class="index-meta">
|
||||
<label>Locations Tracked</label>
|
||||
<label>{{$count}}</label>
|
||||
<label>Total Sources</label>
|
||||
<label>{{$sources}}</label>
|
||||
<label>Latest Update</label>
|
||||
<label>{{$latest_date}}</label>
|
||||
</div>
|
||||
<table class="index-meta">
|
||||
<caption class="visually-hidden">Meta</caption>
|
||||
<tr>
|
||||
<th>Active Locations Tracked</th>
|
||||
<td>{{$count}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total Sources</th>
|
||||
<td>{{$sources}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Latest Update</th>
|
||||
<td>{{$latest_date}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
@ -1,35 +1,35 @@
|
||||
@extends('frame')
|
||||
@section('title', 'The Bad Space|Listings')
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article role="list">
|
||||
<h2>Page {{$pageNum}}</h2>
|
||||
<a href="/listings/{{$prev}}">PREV</a>
|
||||
{{$pageNum}} of {{$totalPages}}
|
||||
<a href="/listings/{{$next}}">NEXT</a><br /><br />
|
||||
@foreach($locations as $location)
|
||||
@php
|
||||
@section('main-content')
|
||||
@parent
|
||||
<section>
|
||||
<article role="list">
|
||||
<h2>Page {{$pageNum}}</h2>
|
||||
<a href="/listings/{{$prev}}">PREV</a>
|
||||
{{$pageNum}} of {{$totalPages}}
|
||||
<a href="/listings/{{$next}}">NEXT</a>
|
||||
@foreach($locations as $location)
|
||||
@php
|
||||
$action = $location->block_count + $location->silence_count;
|
||||
$rating = floor(($action / $sources)*100);
|
||||
@endphp
|
||||
<a class="list-link" role="listitem" href="/location/{{$location->uuid}}">
|
||||
<span class="item-rating">{{$rating}}%</span>
|
||||
<label class="item-name">{{$location->name}}</label>
|
||||
<div class="item-silence">
|
||||
<img class="item-icon" src="/assets/images/global/status-silence.svg" title="silenced" />
|
||||
{{$location->silence_count}}
|
||||
</div>
|
||||
<div class="item-block">
|
||||
<img class="item-icon" src="/assets/images/global/status-suspend.svg" title="suspended" />
|
||||
{{$location->block_count}}
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
<br />
|
||||
<a href="/listings/{{$prev}}">PREV</a>
|
||||
{{$pageNum}} of {{$totalPages}}
|
||||
<a href="/listings/{{$next}}">NEXT</a>
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
||||
@endphp
|
||||
<a class="list-link" role="listitem" href="/location/{{$location->uuid}}">
|
||||
<span class="item-rating">{{$rating}}%</span>
|
||||
<label class="item-name">{{$location->name}}</label>
|
||||
<div class="item-silence">
|
||||
<img class="item-icon" src="/assets/images/global/status-silence.svg" title="silenced" />
|
||||
{{$location->silence_count}}
|
||||
</div>
|
||||
<div class="item-block">
|
||||
<img class="item-icon" src="/assets/images/global/status-suspend.svg" title="suspended" />
|
||||
{{$location->block_count}}
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
<a href="/listings/{{$prev}}">PREV</a>
|
||||
{{$pageNum}} of {{$totalPages}}
|
||||
<a href="/listings/{{$next}}">NEXT</a>
|
||||
<br /><br />
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
@ -6,13 +6,19 @@
|
||||
@parent
|
||||
<section>
|
||||
<article>
|
||||
<h2>Description</h2>
|
||||
{{$location->description}}<br />
|
||||
<h2>Public Comments</h2>
|
||||
@foreach($comments as $comment)
|
||||
@if($comment != " " && $comment != '')
|
||||
{{trim($comment)}}<br /><br />
|
||||
@endif
|
||||
@endforeach
|
||||
<h2>Notes</h2>
|
||||
{{$location->notes}}
|
||||
<h2>References</h2>
|
||||
<h3>Images</h3>
|
||||
@if($images != null)
|
||||
@foreach($images as $image)
|
||||
<a href="/{{$image->path}}" class="location-image" style="background: url(/{{$image->path}}) no-repeat center center / cover #fc6399" />
|
||||
<a href="{{$image->path}}" class="location-image" style="background: url({{$image->path}}) no-repeat center center / cover #fc6399" />
|
||||
</a>
|
||||
@endforeach
|
||||
@endif
|
||||
@ -21,6 +27,9 @@
|
||||
$rating = floor(($action / $sources_count)*100);
|
||||
@endphp
|
||||
<h3>Links</h3>
|
||||
@foreach($links as $link)
|
||||
<a href="{{$link}}">{{$link}}</a><br />
|
||||
@endforeach
|
||||
<div class="location-rating">
|
||||
<div>
|
||||
<img class="rating-icon" src="/assets/images/global/heat.svg" title="heat-rating" />
|
||||
@ -47,8 +56,11 @@
|
||||
|
||||
<br />
|
||||
Heat Rating is the percentage of <a href="/about#how">Current Sources</a> that have taken action against an instance. The higher the number of Sources that have silenced and/or suspended an instance, the higher the Heat Rating.
|
||||
|
||||
<br />
|
||||
<br />UPDATED : {{$updated}}
|
||||
<br />
|
||||
<br />
|
||||
|
||||
</article>
|
||||
</section>
|
||||
@endsection
|
@ -5,8 +5,10 @@ use App\Http\Controllers\FrontIndexController;
|
||||
use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\DenController;
|
||||
use App\Http\Controllers\LocationController;
|
||||
use App\Http\Controllers\MemberController;
|
||||
use App\Http\Controllers\ExportController;
|
||||
use App\Http\Controllers\AppealController;
|
||||
use App\Http\Controllers\SourceController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@ -27,6 +29,7 @@ Route::get("/location/{uuid}", [FrontIndexController::class, 'location']);
|
||||
Route::get("/appeals", [FrontIndexController::class, 'appeals']);
|
||||
Route::post("/search", [FrontIndexController::class, 'indexSearch']);
|
||||
Route::post("/appeal", [AppealController::class, 'sendAppeal']);
|
||||
Route::post("/den/member/admin-create", [MemberController::class, 'adminCreate']);
|
||||
|
||||
//exports
|
||||
Route::get("/exports", [ExportController::class, 'exportIndex']);
|
||||
@ -40,7 +43,6 @@ Route::get("/logout", [AuthController::class, 'leave']);
|
||||
//back
|
||||
Route::group(['prefix' => 'den', 'middleware' => 'member.check'], function () {
|
||||
Route::get("/", [DenController::class, 'start']);
|
||||
Route::get("/member", [DenController::class, 'member']);
|
||||
Route::get("/listings/{pageNum}", [DenController::class, 'location']);
|
||||
Route::get("/location/edit/{uuid}", [DenController::class, 'locationEdit']);
|
||||
Route::get("/locations", [DenController::class, 'locations']);
|
||||
@ -48,4 +50,19 @@ Route::group(['prefix' => 'den', 'middleware' => 'member.check'], function () {
|
||||
Route::post("/locations/edit", [LocationController::class, 'editLocation']);
|
||||
Route::get("/admin/update", [LocationController::class, 'updateLocations']);
|
||||
Route::get("/admin/compile", [LocationController::class, 'compileLocations']);
|
||||
//member stuff
|
||||
Route::get("/you", [MemberController::class, 'profile']);
|
||||
Route::get("/member", [MemberController::class, 'index']);
|
||||
Route::get("/member/{uuid}", [MemberController::class, 'editMember']);
|
||||
Route::get("/member/edit/create", [MemberController::class, 'createMember']);
|
||||
//source stuff
|
||||
Route::get("/sources", [SourceController::class, 'index']);
|
||||
Route::get("/source/{id}", [SourceController::class, 'editSource']);
|
||||
Route::get("/source/edit/create", [SourceController::class, 'createSource']);
|
||||
//actions
|
||||
Route::post("/profile/edit", [MemberController::class, 'profileEdit']);
|
||||
Route::post("/member/edit", [MemberController::class, 'memberEdit']);
|
||||
Route::post("/member/create", [MemberController::class, 'memberCreate']);
|
||||
Route::post("/source/edit", [SourceController::class, 'sourceEdit']);
|
||||
Route::post("/source/create", [SourceController::class, 'sourceCreate']);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user