1
0
mirror of https://koodu.h-i.works/projects/thebadspace synced 2025-05-06 14:41:02 -05:00

Compare commits

..

No commits in common. "develop" and "a0.06" have entirely different histories.

52 changed files with 1858 additions and 3272 deletions

2
.gitignore vendored
View File

@ -4,8 +4,6 @@
/public/hot
/public/storage
/public/reference
/public/assets/images/references
/public/assets/images/members
/storage/*.key
/vendor
.env

View File

@ -55,15 +55,20 @@ 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,
//'single_blank_line_before_namespace' => true, php fixer doesn't like this rule?
'ordered_imports' => [
'sort_algorithm' => 'none',
],
//Other rules here...
])
->setLineEnding("\n");

View File

@ -1,91 +1,9 @@
# The Bad Space
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.
A searcable catalog of the worst places on the web.
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.
More features incoming
## 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.
An Hi Project =)
*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.

View File

@ -34,6 +34,14 @@ 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();

View File

@ -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,18 +60,13 @@ class ExportController extends Controller
if ($rate * 100 >= $percent) {
if ($type == 'mastodon') {
//comman break teh CSV so just take them out
$comments = str_replace(",", ";", $location->public_comments);
$comments = str_replace(",", ";", $location->description);
//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, $rating, $comments, "FALSE", "FALSE", "FALSE"]);
array_push($list, [$location->url, $location->rating, $comments, "FALSE", "FALSE", "FALSE"]);
}
}
}

View File

@ -6,49 +6,33 @@ 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()
{
//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"
]);
}
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"
]);
}
public function indexSearch(Request $request)
@ -92,19 +76,15 @@ class FrontIndexController extends Controller
}
if (isset($member->role)) {
($member->role == 0 || $member->role == 1) ? $edit = true : $edit = false;
($member->role == 1 || $member->role == 2) ? $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
]);

View File

@ -5,7 +5,6 @@ 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
{
@ -19,51 +18,34 @@ class LocationController extends Controller
$this->location = $locationRepository;
}
//actions
public function updateLocations()
{
//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');
}
$result = $this->update->data();
return back()->with(
'message',
$result
);
}
public function compileLocations()
{
//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');
}
$result = $this->update->list();
return back()->with(
'message',
$result
);
}
public function editLocation(Request $request)
{
$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']);
}
$token = csrf_token();
$response = $this->location->editLocation($request);
if ($response['status']) {
return back()->with('message', $response['message']);
} else {
return back()->withErrors('message', 'Nah, you don\'t have permission to do this');
return back()->withErrors('message', $response['message']);
}
}
}

View File

@ -1,182 +0,0 @@
<?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.']);
}
}
}

View File

@ -1,102 +0,0 @@
<?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.']);
}
}
}

View File

@ -24,7 +24,7 @@ class Location extends Model
"uuid",
"name",
"url",
"public_comments",
"description",
"images",
"active",
"rating",
@ -35,16 +35,6 @@ class Location extends Model
"created_at",
"updated_at",
"actions_count",
"archive_links",
"notes",
"archive_links"
];
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]);
}
}

View File

@ -9,20 +9,6 @@ class Member extends Authenticatable
{
use HasFactory;
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"
];
protected $table = "member";
protected $fillable = ["uuid", "handle", "email", "password", "active", "role", "avatar", "pronoun", "gender"];
}

View File

@ -5,12 +5,10 @@ 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
{
@ -27,10 +25,6 @@ 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()),

View File

@ -23,8 +23,7 @@ class LocationRepository
$rawSearch = $terms;
$terms = str_replace(",", "", $terms);
$terms = str_replace(" ", "|", $terms);
//$raw = DB::select("SELECT * FROM searchlocations(?)", [$terms]);
$raw = Location::search($terms)->get();
$raw = DB::select("SELECT * FROM searchlocations(?)", [$terms]);
$results = [];
foreach ($raw as $item) {
if (($item->block_count + $item->silence_count) >= 2) {
@ -57,34 +56,24 @@ class LocationRepository
public function editLocation($request)
{
$location = $this->getLocation($request->id);
$publicPath = '../public/';
$refPath = 'assets/images/references/' . $location->uuid;
$images = [];
$location = $this->getLocation($request->id);
$images = [];
if ($request->hasfile("references")) {
foreach ($request->references as $file) {
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]);
$path = $file->store('reference');
array_push($images, ["path" => $path]);
}
}
if (!empty($images)) {
$request->merge(['images' => json_encode($images)]);
$location->images = json_encode($images);
}
$request->merge(['images' => json_encode($images)]);
$location->name = $request->name;
$location->notes = $request->notes;
$location->description = $request->description;
$location->archive_links = $request->archive_links;
$location->images = json_encode($images);
$result = [];
if ($location->save()) {
return ['status' => true, 'message' => "Location Editited" . $request->hasfile("references")];
return ['status' => true, 'message' => "Location Editited -IMG- " . $request->hasfile("references")];
} else {
return ['status' => false, 'message' => "Location Not Editited"];
}

View File

@ -1,145 +0,0 @@
<?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"];
}
}
}

View File

@ -9,26 +9,10 @@ use GuzzleHttp\Exception\ConnectException;
class SourceRepository
{
protected $source;
protected $missing;
protected $updated;
protected $sources;
public function __construct(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();
$this->source = $source;
}
public function getActive()
@ -36,120 +20,51 @@ class SourceRepository
return $this->source::where("active", true)->get();
}
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)
public function updateSourceData()
{
$sources = $this->getActive();
$missing = [];
$checked = [];
//checks all the sources to refresh data
$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);
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]);
}
} else {
//if empty run the same index again
array_push($this->missing, ['source' => $source->url]);
$result = $this->updateSourceData($index);
try {
$result = \Mastodon::domain('https://' . $source['url'])
->token($source['token'])
->get('/instance/domain_blocks');
array_push($checked, ['source' => $source->url]);
} catch (ConnectException $e) {
}
}
} 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]);
}
} 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 {
$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
}
}
} 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]);
}
$source->list_data = json_encode($result);
$source->last_updated = Carbon::now();
$source->save();
}
return $result;
return ['checked' => $checked, 'notchecked' => $missing];
}
}

View File

@ -23,11 +23,8 @@ class UpdateService
public function data()
{
$response = $this->source->updateSourceData();
if ($response['updated'] == 'true') {
return count($response['checked']) . ' SOURCES UPDATED';
} else {
return 'NO SOURCES PRESENT';
}
return count($response['checked']) . ' SOURCES UPDATED - ' .
count($response['notchecked']) . ' SOURCES NOT CHECKED';
}
public function list()
@ -36,15 +33,14 @@ class UpdateService
$fresh = 0;
$unified = [];
$sources = $this->source->getActive();
$locations = $this->location->getActiveLocations();
$sources = $this->source->getActive();
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 and comment
//if there is a match, update the count
if ($item->severity == "suspend" || $item->severity == "defederate") {
++$unified[$index]['block_count'];
array_push($unified[$index]['block_vote'], $source->url);
@ -52,9 +48,6 @@ 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;
@ -81,12 +74,6 @@ 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) {
@ -99,12 +86,6 @@ 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'];
@ -131,20 +112,20 @@ class UpdateService
}
$new = Location::create([
'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']
'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']
]);
}
}

View File

@ -1,39 +1,14 @@
{
"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"
},
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"php": "^8.1",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.0",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8",
"revolution/laravel-mastodon-api": "^3.0"
},
@ -42,8 +17,8 @@
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^11.0",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"
},
"autoload": {
@ -71,17 +46,11 @@
],
"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": {

2775
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
<?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');
}
};

View File

@ -0,0 +1,28 @@
<?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');
}
};

View File

@ -0,0 +1,32 @@
<?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');
}
};

View File

@ -0,0 +1,33 @@
<?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');
}
};

View File

@ -1,57 +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('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');
}
};

View File

@ -1,37 +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('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');
}
};

View File

@ -1,34 +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('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');
}
};

View File

@ -1,35 +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('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');
}
};

View File

@ -3,12 +3,6 @@ section.index-search {
background: var(--white);
}
/* TODO: move to a global file? its 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;
@ -18,57 +12,36 @@ 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 {
display: grid;
grid-template-columns: auto;
align-items: center;
padding: 0;
height: 60px;
width: 60px;
text-transform: uppercase;
position: relative;
top: 9px;
right: 0;
}
form.index-search-form > button > svg {
justify-self: center;
width: 48px;
height: 48px;
form.index-search-form > button > img#search-icon {
float: none;
}
form.index-search-form > button > span {
display: none;
margin-top: 3px;
form.index-search-form > button > label {
font-weight: 500;
top: 15px;
position: relative;
font-size: 1.5em;
display: none;
}
::placeholder {
@ -77,65 +50,59 @@ form.index-search-form > button > span {
}
section.index-meta article {
padding-block: 30px;
margin-top: 20px;
}
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;
div.index-meta {
display: grid;
grid-template-columns: 50% 50%;
gap: 10px;
width: 98%;
font-weight: 500;
}
table.index-meta th {
color: var(--secondary);
text-align: left;
}
table.index-meta td {
padding-inline-start: 10px;
div.index-meta > label:nth-child(2),
div.index-meta > label:nth-child(4),
div.index-meta > label:nth-child(6) {
color: var(--white);
width: 100%;
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: 100%;
width: 99%;
top: 15px;
}
form.index-search-form > button {
grid-template-columns: auto 60px;
form.index-search-form > button > label {
display: inline;
}
form.index-search-form > button span {
display: block;
form.index-search-form > button > img#search-icon {
float: right;
}
}

View File

@ -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: 1fr auto 30px 30px;
grid-template-rows: 100% 100px 30px 30px;
gap: 5px;
height: auto;
padding-bottom: 20px;

View File

@ -1,4 +1,3 @@
@import "../global/utilities.css";
@import "../global/colors.css";
@import "../global/forms.css";
@import "../global/typography.css";

View File

@ -8,25 +8,16 @@ input[type="text"] {
display: inline-block;
background: var(--white);
color: var(--primary);
transition: 0.2s linear;
transition-property: color, background-color;
transition: all 0.2s linear;
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;
@ -43,10 +34,8 @@ input[type="submit"] {
position: relative;
cursor: pointer;
border: 0;
transition: 0.3s linear;
transition-property: color, background-color;
transition: all 0.3s linear;
height: 35px;
margin-top: 15px;
}
select {
@ -56,5 +45,4 @@ select {
appearance: none;
color: var(--primary);
background: var(--secondary);
height: 35px;
}

View File

@ -199,7 +199,9 @@ footer {
padding: 10px;
gap: 10px;
height: auto;
width: auto;
width: 80%;
margin: 20px auto;
max-width: 1000px;
position: relative;
}
@ -211,15 +213,6 @@ footer > div:nth-child(2) {
text-align: right;
}
/*
member stuff
*/
.your-avatar {
width: 250px;
border-radius: 5px;
}
/*
responsive
*/

View File

@ -67,11 +67,6 @@ h3 {
font-weight: 500;
}
h3.strong {
color: var(--secondary);
font-weight: bolder;
}
@media only screen and (max-width: 800px) {
h1 {
font-size: 2em;

View File

@ -1,13 +0,0 @@
/**
* 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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,4 +1,8 @@
<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"/>
<?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>

Before

Width:  |  Height:  |  Size: 571 B

After

Width:  |  Height:  |  Size: 729 B

View File

@ -11,10 +11,8 @@
@csrf
<label>Edit Location Name</label><br>
<input type="text" name="name" value="{{$location->name}}" /><br>
<label>Edit Location Notes</label><br>
<textarea name="notes">{{$location->notes}}</textarea><br>
<label>Edit Location Comments</label><br>
<textarea name="description">{{$location->description}}</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>
@ -26,7 +24,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

View File

@ -2,51 +2,12 @@
@section('title', 'Den | Member Admin')
@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
@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

View File

@ -1,13 +0,0 @@
@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

View File

@ -1,42 +0,0 @@
@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

View File

@ -2,17 +2,16 @@
@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 />
@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
<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

View File

@ -1,74 +0,0 @@
<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>

View File

@ -1,30 +0,0 @@
<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>

View File

@ -1,66 +0,0 @@
<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>

View File

@ -2,101 +2,100 @@
<html>
<head>
<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
<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
</head>
<body>
<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>
</header>
@if($errors->any())
<div class="system-notice-error" role="status">
{{$errors->first()}}
<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>
</div>
@endif
@if(session('message'))
<div class="system-notice-message" role="status">
{!! session('message') !!}
</div>
@endif
</div>
</div>
</header>
<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>
@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>
</body>
</html>
</html>

View File

@ -1,81 +1,89 @@
@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>
@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>
<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>
<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.</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>
<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>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>
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>.
<h3 class="strong">Current Sources:</h3>
<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
<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>
<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>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"
<pre>
<code>{
data:{
"listingCount":1,
"locations":
[
{
"url":"search.url",
"name":"Instance Name",
"description":"instance description",
"link":"bad-space-instance-link"
}
]
}
]
}
}</code>
</pre>
</article>
</section>
@endsection
}</code>
</pre>
</p>
</article>
</section>
@endsection

View File

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

View File

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

View File

@ -1,15 +1,13 @@
@extends('frame')
@extends('frame')
@section('title', 'This is The Bad Space')
@section('main-content')
@parent
<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-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 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>
@csrf
</form>
@ -40,21 +38,33 @@
@endisset
<section class="index-meta">
<article>
<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>
<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>
</article>
</section>
@endsection
@endsection

View File

@ -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>
@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><br /><br />
@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
<a href="/listings/{{$prev}}">PREV</a>
{{$pageNum}} of {{$totalPages}}
<a href="/listings/{{$next}}">NEXT</a>
<br /><br />
</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
<br />
<a href="/listings/{{$prev}}">PREV</a>
{{$pageNum}} of {{$totalPages}}
<a href="/listings/{{$next}}">NEXT</a>
</article>
</section>
@endsection

View File

@ -6,19 +6,13 @@
@parent
<section>
<article>
<h2>Public Comments</h2>
@foreach($comments as $comment)
@if($comment != " " && $comment != '')
{{trim($comment)}}<br /><br />
@endif
@endforeach
<h2>Notes</h2>
{{$location->notes}}
<h2>Description</h2>
{{$location->description}}<br />
<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
@ -27,9 +21,6 @@
$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" />
@ -56,11 +47,8 @@
<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 />
<br />UPDATED : {{$updated}}
</article>
</section>
@endsection

View File

@ -5,10 +5,8 @@ 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;
/*
|--------------------------------------------------------------------------
@ -29,7 +27,6 @@ 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']);
@ -43,6 +40,7 @@ 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']);
@ -50,19 +48,4 @@ 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']);
});