Commit 48a55026 authored by SpinShare's avatar SpinShare

added api login and notifications

parent 2aecb876
...@@ -27,9 +27,9 @@ security: ...@@ -27,9 +27,9 @@ security:
always_remember_me: true always_remember_me: true
access_control: access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/upload$, role: ROLE_USER } - { path: ^/upload, role: ROLE_USER }
- { path: ^/settings$, role: ROLE_USER } - { path: ^/settings, role: ROLE_USER }
- { path: ^/moderation$, role: ROLE_MODERATOR } - { path: ^/moderation, role: ROLE_MODERATOR }
\ No newline at end of file \ No newline at end of file
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
# Put parameters here that don't need to change on each machine where the app is deployed # Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters: parameters:
api_version: 1
srtb_path: "%kernel.project_dir%/public/uploads/srtb" srtb_path: "%kernel.project_dir%/public/uploads/srtb"
cover_path: "%kernel.project_dir%/public/uploads/cover" cover_path: "%kernel.project_dir%/public/uploads/cover"
audio_path: "%kernel.project_dir%/public/uploads/audio" audio_path: "%kernel.project_dir%/public/uploads/audio"
......
# SpinShare API
## Connect API
### Introduction
Welcome to the API documentations to the SpinShare Connect API. This document outlines how to connect a SpinShare userprofile with your application and how you can use this connection to perform user actions such as creating reviews or adding charts to playlists.
### Terminology
| Name | Description |
| ---- | ----------- |
| User | A "User" is a SpinShare userprofile. |
| ConnectApp | A "ConnectApp" is your application, bot or whatever. |
| Connection | A "Connection" is a relationship between a ConnectApp and a User. A User can always revoke access to a ConnectApp by going into the usersettings on SpinShare |
| ConnectCode | A "ConnectCode" is a 6 character code that is used for establishing the connection between a User and a ConnectApp. This code will change every 15 seconds as long as the user is on the SpinShare connect page. |
| ConnectToken | A "ConnectToken" is a series of characters used along with your ApiKey to verify you are allowed to perform certain actions and ping certain api endpoints. This token is valid until the user revokes access. |
### Getting Access
API access is handled privately as of now. Please refer to the developers on the SpinShare Discord to receive API access.
### Creating a Connection
The SpinShare API does not consume login credentials for protected API endpoints but rather a simple apikey/token solution. Users have to connect to a ConnectApp once by inputting a 6 character long ConnectCode that changes every 15 seconds.
**Steps to establish a Connection**
- Prompt the user to input their connect code. The code can be found on within the profile settings under the "Connect" tab.
- Put the ConnectCode through the /getToken API endpoint along with your ApiKey to generate a ConnectToken.
- Save the ConnectToken locally and use it for protected API endpoints. If you need to verify if your ConnectToken is still valid, you can use the /validateToken API endpoint.
### API Endpoints
#### /api/connect/getToken
This API endpoint gives you a ConnectToken.
**Parameters**
(string) connectCode
(string) connectAppApiKey
**Response**
200 - OK. The body contains the ConnectToken
400 - Parameters are missing. The body contains the needed parameters and their value
404 - The ConnectCode or ApiKey was wrong.
#### /api/connect/validateToken
This API endpoint checks if a ConnectToken is still valid.
**Parameters**
(string) connectToken
**Response**
200 - The ConnectToken is valid.
404 - The ConnectToken is not valid.
\ No newline at end of file
...@@ -170,3 +170,90 @@ ...@@ -170,3 +170,90 @@
letter-spacing: 0.1em; letter-spacing: 0.1em;
font-size: 14px; font-size: 14px;
} }
.section-connect {
padding: 50px;
display: grid;
grid-template-columns: 300px 1fr;
grid-gap: 25px;
}
.section-connect .connect-title {
margin-bottom: 10px;
text-transform: uppercase;
font-size: 12px;
letter-spacing: 0.25em;
font-weight: bold;
}
.section-connect .connect-content {
width: 300px;
display: grid;
}
.section-connect .connect-content .connect-code {
width: 300px;
text-align: center;
display: inline-block;
font-size: 32px;
letter-spacing: 0.25em;
font-weight: 300;
padding: 20px;
background: #000;
border-radius: 4px;
font-family: 'Oswald', sans-serif;
}
.section-connect .connect-content .connect-timeout {
background: rgba(255, 255, 255, 0.4);
border-radius: 10px;
margin-top: 5px;
height: 5px;
overflow: hidden;
}
.section-connect .connect-content .connect-timeout .connect-progress {
height: 5px;
width: 0%;
background: #e22c78;
}
.section-connect .connect-content .connect-explaination {
margin-top: 25px;
opacity: 0.6;
line-height: 1.5;
text-align: center;
}
.section-connect .connections-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 15px;
}
.section-connect .connections-list .connection-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
padding: 25px;
}
.section-connect .connections-list .connection-item .connection-title {
font-weight: 600;
font-size: 18px;
margin-bottom: 5px;
}
.section-connect .connections-list .connection-item .connection-connected {
opacity: 0.6;
margin-bottom: 30px;
}
.section-connect .connections-list .noconnection-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
padding: 25px;
}
.section-connect .connections-list .noconnection-item .noconnection-title {
font-weight: 600;
font-size: 18px;
margin-bottom: 5px;
}
.section-connect .connections-list .noconnection-item .noconnection-connected {
opacity: 0.6;
}
@keyframes timeoutProgress {
from {
width: 100%;
}
to {
width: 0%;
}
}
...@@ -163,3 +163,99 @@ ...@@ -163,3 +163,99 @@
} }
} }
} }
.section-connect {
padding: 50px;
display: grid;
grid-template-columns: 300px 1fr;
grid-gap: 25px;
& .connect-title {
margin-bottom: 10px;
text-transform: uppercase;
font-size: 12px;
letter-spacing: 0.25em;
font-weight: bold;
}
& .connect-content {
width: 300px;
display: grid;
& .connect-code {
width: 300px;
text-align: center;
display: inline-block;
font-size: 32px;
letter-spacing: 0.25em;
font-weight: 300;
padding: 20px;
background: #000;
border-radius: 4px;
font-family: 'Oswald', sans-serif;
}
& .connect-timeout {
background: rgba(255,255,255,0.4);
border-radius: 10px;
margin-top: 5px;
height: 5px;
overflow: hidden;
& .connect-progress {
height: 5px;
width: 0%;
background: rgb(226, 44, 120);
}
}
& .connect-explaination {
margin-top: 25px;
opacity: 0.6;
line-height: 1.5;
text-align: center;
}
}
& .connections-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 15px;
& .connection-item {
background: rgba(255,255,255,0.1);
border-radius: 4px;
padding: 25px;
& .connection-title {
font-weight: 600;
font-size: 18px;
margin-bottom: 5px;
}
& .connection-connected {
opacity: 0.6;
margin-bottom: 30px;
}
}
& .noconnection-item {
background: rgba(255,255,255,0.1);
border-radius: 4px;
padding: 25px;
& .noconnection-title {
font-weight: 600;
font-size: 18px;
margin-bottom: 5px;
}
& .noconnection-connected {
opacity: 0.6;
}
}
}
}
@keyframes timeoutProgress {
from {
width: 100%;
}
to {
width: 0%;
}
}
\ No newline at end of file
<?php
namespace App\Controller\API;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\ClientRelease;
use App\Entity\Song;
use App\Entity\SongReview;
use App\Entity\SongSpinPlay;
use App\Entity\User;
use App\Entity\Promo;
class APIClientController extends AbstractController
{
/**
* @Route("/api/latestVersion/{platform}", name="api.latestVersion")
*/
public function latestVersion(string $platform)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$latestVersion = $em->getRepository(ClientRelease::class)->findOneBy(array('platform' => $platform), array('majorVersion' => 'DESC', 'minorVersion' => 'DESC', 'patchVersion' => 'DESC'));
$data['stringVersion'] = $latestVersion->getMajorVersion().".".$latestVersion->getMinorVersion().".".$latestVersion->getPatchVersion();
$data['majorVersion'] = $latestVersion->getMajorVersion();
$data['minorVersion'] = $latestVersion->getMinorVersion();
$data['patchVersion'] = $latestVersion->getPatchVersion();
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $data]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
This diff is collapsed.
<?php
namespace App\Controller\API;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\ClientRelease;
use App\Entity\Song;
use App\Entity\SongReview;
use App\Entity\SongSpinPlay;
use App\Entity\User;
use App\Entity\Promo;
class APIPingController extends AbstractController
{
/**
* @Route("/api/ping", name="api.ping")
*/
public function ping()
{
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'pong' => true]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
<?php
namespace App\Controller\API;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\ClientRelease;
use App\Entity\Song;
use App\Entity\SongReview;
use App\Entity\SongSpinPlay;
use App\Entity\User;
use App\Entity\Promo;
class APIPromosController extends AbstractController
{
/**
* @Route("/api/promos", name="api.promos")
*/
public function promos(Request $request)
{
$em = $this->getDoctrine()->getManager();
$results = $em->getRepository(Promo::class)->findBy(array('isVisible' => true), array('id' => 'DESC'), 2);
$baseUrl = $request->getScheme() . '://' . $request->getHttpHost() . $request->getBasePath();
if(!$results) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
foreach($results as $result) {
$oneResult = [];
$oneResult['id'] = $result->getId();
$oneResult['title'] = $result->getTitle();
$oneResult['type'] = $result->getType();
$oneResult['textColor'] = $result->getTextColor();
$oneResult['color'] = $result->getColor();
$oneResult['button']['type'] = $result->getButtonType();
$oneResult['button']['data'] = $result->getButtonData();
$oneResult['isVisible'] = $result->getIsVisible();
$oneResult['image_path'] = $baseUrl."/uploads/promo/".$result->getImagePath();
$data[] = $oneResult;
}
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $data]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
}
<?php
namespace App\Controller\API;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\ClientRelease;
use App\Entity\Song;
use App\Entity\SongReview;
use App\Entity\SongSpinPlay;
use App\Entity\User;
use App\Entity\Promo;
class APISongController extends AbstractController
{
/**
* @Route("/api/song/{idOrReference}", name="api.songs.detail")
*/
public function songDetail(Request $request, $idOrReference)
{
$em = $this->getDoctrine()->getManager();
$data = [];
if(is_numeric($idOrReference)) {
// $idOrReference is the ID
$result = $em->getRepository(Song::class)->findOneBy(array('id' => $idOrReference));
} else {
// $idOrReference is the file Reference
$result = $em->getRepository(Song::class)->findOneBy(array('fileReference' => $idOrReference));
}
$baseUrl = $request->getScheme() . '://' . $request->getHttpHost() . $request->getBasePath();
if(!$result) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$result->setViews($result->getViews() + 1);
$em->persist($result);
$em->flush();
$data['id'] = $result->getId();
$data['title'] = $result->getTitle();
$data['subtitle'] = $result->getSubtitle();
$data['artist'] = $result->getArtist();
$data['charter'] = $result->getCharter();
$data['hasEasyDifficulty'] = $result->getHasEasyDifficulty();
$data['hasNormalDifficulty'] = $result->getHasNormalDifficulty();
$data['hasHardDifficulty'] = $result->getHasHardDifficulty();
$data['hasExtremeDifficulty'] = $result->getHasExtremeDifficulty();
$data['hasXDDifficulty'] = $result->getHasXDDifficulty();
$data['uploader'] = $result->getUploader();
$data['uploadDate'] = $result->getUploadDate();
$data['tags'] = $result->getTagsArray();
$data['fileReference'] = $result->getFileReference();
$data['paths']['ogg'] = $baseUrl."/uploads/audio/".$result->getFileReference()."_0.ogg";
$data['paths']['cover'] = $baseUrl."/uploads/thumbnail/".$result->getFileReference().".jpg";
$data['paths']['zip'] = $this->generateUrl('api.songs.download', array('id' => $result->getId()), UrlGeneratorInterface::ABSOLUTE_URL);
$data['description'] = $result->getDescription();
$data['views'] = $result->getViews();
$data['downloads'] = $result->getDownloads();
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $data]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
/**
* @Route("/api/song/{idOrReference}/reviews", name="api.songs.detail.reviews")
*/
public function songDetailReviews(Request $request, $idOrReference)
{
$em = $this->getDoctrine()->getManager();
$data = [];
if(is_numeric($idOrReference)) {
// $idOrReference is the ID
$resultSong = $em->getRepository(Song::class)->findOneBy(array('id' => $idOrReference));
} else {
// $idOrReference is the file Reference
$resultSong = $em->getRepository(Song::class)->findOneBy(array('fileReference' => $idOrReference));
}
$baseUrl = $request->getScheme() . '://' . $request->getHttpHost() . $request->getBasePath();
if(!$resultSong) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$resultReviewAverage = $em->getRepository(SongReview::class)->getAveragebyID($resultSong->getId());
$data['average'] = $resultReviewAverage;
$resultReviews = $em->getRepository(SongReview::class)->findBy(array('song' => $resultSong), array('reviewDate' => 'DESC'));
foreach($resultReviews as $review) {
$data['reviews'][] = $review->getJSON();
}
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $data]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
/**
* @Route("/api/song/{idOrReference}/spinplays", name="api.songs.detail.spinplays")
*/
public function songDetailSpinPlays(Request $request, $idOrReference)
{
$em = $this->getDoctrine()->getManager();
$data = [];
if(is_numeric($idOrReference)) {
// $idOrReference is the ID
$resultSong = $em->getRepository(Song::class)->findOneBy(array('id' => $idOrReference));
} else {
// $idOrReference is the file Reference
$resultSong = $em->getRepository(Song::class)->findOneBy(array('fileReference' => $idOrReference));
}
$baseUrl = $request->getScheme() . '://' . $request->getHttpHost() . $request->getBasePath();
if(!$resultSong) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$resultSpinPlays = $em->getRepository(SongSpinPlay::class)->findBy(array('song' => $resultSong, 'isActive' => true), array('submitDate' => 'DESC'));
foreach($resultSpinPlays as $spinPlay) {
$data['spinPlays'][] = $spinPlay->getJSON();
}
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $data]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
/**
* @Route("/api/song/{id}/download", name="api.songs.download")
*/
public function songDownload(int $id)
{
$em = $this->getDoctrine()->getManager();
$result = $em->getRepository(Song::class)->findOneBy(array('id' => $id));
$zipLocation = $this->getParameter('temp_path').DIRECTORY_SEPARATOR;
$zipName = $result->getFileReference().".zip";
if(!$result) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
try {
$coverFiles = glob($this->getParameter('cover_path').DIRECTORY_SEPARATOR.$result->getFileReference().".png");
$oggFiles = glob($this->getParameter('audio_path').DIRECTORY_SEPARATOR.$result->getFileReference()."_*.ogg");
$zip = new \ZipArchive;
$zip->open($zipLocation.$zipName, \ZipArchive::CREATE);
$zip->addFile($this->getParameter('srtb_path').DIRECTORY_SEPARATOR.$result->getFileReference().".srtb", $result->getFileReference().".srtb");
if(is_file($coverFiles[0])) {
$zip->addFile($coverFiles[0], "AlbumArt".DIRECTORY_SEPARATOR.$result->getFileReference().".png");
}
if(count($oggFiles) > 0) {
foreach($oggFiles as $oggFile) {
$oggIndex = explode(".", explode("_", $oggFile)[2])[0];
$zip->addFile($oggFile, "AudioClips".DIRECTORY_SEPARATOR.$result->getFileReference()."_".$oggIndex.".ogg");
}
}
$zip->close();
$response = new Response(file_get_contents($zipLocation.$zipName));
$response->headers->set('Content-Type', 'application/zip');
$response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"');
$response->headers->set('Content-length', filesize($zipLocation.$zipName));
} catch(Exception $e) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 500, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
@unlink($zipLocation.$zipName);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
}
<?php
namespace App\Controller\API;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\ClientRelease;
use App\Entity\Song;
use App\Entity\SongReview;
use App\Entity\SongSpinPlay;
use App\Entity\User;
use App\Entity\Promo;
class APIStreamStatusController extends AbstractController
{
/**
* @Route("/api/streamStatus", name="api.streamStatus")
*/
public function streamStatus()
{
$data = [];
$successful = false;
try {
$client = HttpClient::create();
$apiAccessResponseRaw = $client->request('POST', 'https://id.twitch.tv/oauth2/token?client_id='.$_ENV['TWITCH_API_CLIENT_ID'].'&client_secret='.$_ENV['TWITCH_API_CLIENT_SECRET'].'&grant_type=client_credentials');
$apiAccessResponse = json_decode($apiAccessResponseRaw->getContent());
$apiAccessToken = $apiAccessResponse->access_token;
$apiResponseRaw = $client->request('GET', 'https://api.twitch.tv/helix/streams/?user_login=spinshare', [
'headers' => [
'Client-ID' => $_ENV['TWITCH_API_CLIENT_ID'],
'Authorization' => 'Bearer '.$apiAccessToken
],
]);
$apiResponse = json_decode($apiResponseRaw->getContent());
$successful = true;
if(count($apiResponse->data) != 0) {
$data = [
"title" => $apiResponse->data[0]->title,
"viewers" => $apiResponse->data[0]->viewer_count,
"isLive" => ($apiResponse->data[0]->type == "live") ? true : false
];
} else {
$data = [
"title" => "",
"viewers" => 0,
"isLive" => false
];
}
} catch(\Exception $e) {
$successful = false;
$data = [];
}
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => $successful ? 200 : 500, 'data' => $data]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
<?php
namespace App\Controller\API;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\ClientRelease;
use App\Entity\Song;
use App\Entity\SongReview;
use App\Entity\SongSpinPlay;
use App\Entity\User;
use App\Entity\Promo;
class APIUserController extends AbstractController
{
/**
* @Route("/api/user/{userId}", name="api.users.detail")
*/
public function userDetail(Request $request, int $userId)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$result = $em->getRepository(User::class)->findOneBy(array('id' => $userId));
$baseUrl = $request->getScheme() . '://' . $request->getHttpHost() . $request->getBasePath();
if(!$result) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$data['id'] = $result->getId();
$data['username'] = $result->getUsername();
$data['isVerified'] = $result->getIsVerified();
$data['isPatreon'] = $result->getIsPatreon();
if($result->getCoverReference()) {
$data['avatar'] = $baseUrl."/uploads/avatar/".$result->getCoverReference();
} else {
$data['avatar'] = $baseUrl."/assets/img/defaultAvatar.jpg";
}
// Get User Songs
$resultsSongs = $em->getRepository(Song::class)->findBy(array('uploader' => $result->getId()), array('uploadDate' => 'DESC'));
$data['songs'] = [];
foreach($resultsSongs as $result) {
$oneResult = [];
$oneResult['id'] = $result->getId();
$oneResult['title'] = $result->getTitle();
$oneResult['subtitle'] = $result->getSubtitle();
$oneResult['artist'] = $result->getArtist();
$oneResult['charter'] = $result->getCharter();
$oneResult['uploader'] = $result->getUploader();
$oneResult['hasEasyDifficulty'] = $result->getHasEasyDifficulty();
$oneResult['hasNormalDifficulty'] = $result->getHasNormalDifficulty();
$oneResult['hasHardDifficulty'] = $result->getHasHardDifficulty();
$oneResult['hasExtremeDifficulty'] = $result->getHasExtremeDifficulty();
$oneResult['hasXDDifficulty'] = $result->getHasXDDifficulty();
$oneResult['cover'] = $baseUrl."/uploads/thumbnail/".$result->getFileReference().".jpg";
$oneResult['zip'] = $this->generateUrl('api.songs.download', array('id' => $result->getId()), UrlGeneratorInterface::ABSOLUTE_URL);
$data['songs'][] = $oneResult;
}
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $data]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
}
<?php
namespace App\Controller\API\Connect;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\User;
use App\Entity\Connection;
use App\Entity\ConnectApp;
class APIConnectController extends AbstractController
{
/**
* @Route("/api/connect/generateCode", name="api.connect.generateCode")
*/
public function generateCode()
{
$em = $this->getDoctrine()->getManager();
$data = [];
$user = $em->getRepository(User::class)->findOneBy(array('id' => $this->getUser()->getID()));
if($user) {
$newCode = substr(md5(time() * $user->getID()), 0, 6);
$user->setConnectCode($newCode);
$em->persist($user);
$em->flush();
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $newCode]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
/**
* @Route("/api/connect/getToken", name="api.connect.getToken")
*/
public function getToken(Request $request)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$connectCode = $request->query->get('connectCode');
$connectAppApiKey = $request->query->get('connectAppApiKey');
if($connectCode == "" || $connectAppApiKey == "") {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 400, 'data' => ["connectCode" => $connectCode, "connectAppApiKey" => $connectAppApiKey]]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
$user = $em->getRepository(User::class)->findOneBy(array('connectCode' => $connectCode));
$connectApp = $em->getRepository(ConnectApp::class)->findOneBy(array('apiKey' => $connectAppApiKey));
if($user && $connectApp) {
$newConnectToken = md5(time() * $user->getID());
// Create Connection
$newConnection = new Connection();
$newConnection->setUser($user);
$newConnection->setApp($connectApp);
$newConnection->setConnectDate(new \DateTime('now'));
$newConnection->setConnectToken($newConnectToken);
// Output Connection Token
// Invalidate ConnectionCode
$user->setConnectCode("");
$em->persist($user);
$em->persist($newConnection);
$em->flush();
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => $newConnectToken]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
/**
* @Route("/api/connect/validateToken/", name="api.connect.validateToken")
*/
public function validateToken(Request $request)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$connectToken = $request->query->get('connectToken');
$connection = $em->getRepository(Connection::class)->findOneBy(array('connectToken' => $connectToken));
if($connectToken != "" && $connection) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
}
<?php
namespace App\Controller\API\Connect;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpClient\HttpClient;
use App\Entity\User;
use App\Entity\Connection;
use App\Entity\ConnectApp;
class APIConnectUserController extends AbstractController
{
/**
* @Route("/api/connect/profile/", name="api.connect.profile")
*/
public function getProfile(Request $request)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$connectToken = $request->query->get('connectToken');
// TODO: Find Connection
$user = $em->getRepository(User::class)->findOneBy(array('connectToken' => $connectToken));
if($connectToken != "" && $user) {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 200, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
} else {
$response = new JsonResponse(['version' => $this->getParameter('api_version'), 'status' => 404, 'data' => []]);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
}
}
...@@ -26,17 +26,6 @@ class IndexController extends AbstractController ...@@ -26,17 +26,6 @@ class IndexController extends AbstractController
$activePromos = $em->getRepository(Promo::class)->findBy(array('isVisible' => true), array('id' => 'DESC'), 2); $activePromos = $em->getRepository(Promo::class)->findBy(array('isVisible' => true), array('id' => 'DESC'), 2);
$data['promos'] = $activePromos; $data['promos'] = $activePromos;
/* $newOffset = $request->query->get('newOffset');
$popularOffset = $request->query->get('popularOffset');
$resultsNewSongs = $em->getRepository(Song::class)->findBy(array(), array('id' => 'DESC'), 6, $newOffset * 6);
$resultsPopularSongs = $em->getRepository(Song::class)->findBy(array(), array('downloads' => 'DESC', 'views' => 'DESC'), 6, $popularOffset * 6);
$data['newSongs'] = $resultsNewSongs;
$data['newOffset'] = $newOffset;
$data['popularSongs'] = $resultsPopularSongs;
$data['popularOffset'] = $popularOffset; */
return $this->render('index/index.html.twig', $data); return $this->render('index/index.html.twig', $data);
} }
......
...@@ -14,6 +14,8 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; ...@@ -14,6 +14,8 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use App\Utils\HelperFunctions; use App\Utils\HelperFunctions;
use App\Entity\User;
use App\Entity\UserNotification;
use App\Entity\Song; use App\Entity\Song;
class SystemController extends AbstractController class SystemController extends AbstractController
...@@ -29,6 +31,29 @@ class SystemController extends AbstractController ...@@ -29,6 +31,29 @@ class SystemController extends AbstractController
return $this->render('moderation/system/index.html.twig', $data); return $this->render('moderation/system/index.html.twig', $data);
} }
/**
* @Route("/moderation/system/notificationToEveryone", name="moderation.system.notificationToEveryone")
*/
public function systemNotificationToEveryone(Request $request)
{
$em = $this->getDoctrine()->getManager();
$allUsers = $em->getRepository(User::class)->findAll();
foreach($allUsers as $user) {
$newNotification = new UserNotification();
$newNotification->setUser($user);
$newNotification->setNotificationType(0);
$newNotification->setNotificationData($request->request->get('notificationData'));
$em->persist($newNotification);
}
$em->flush();
return $this->redirectToRoute('moderation.system.index');
}
/** /**
* @Route("/moderation/system/cleanup/temp", name="moderation.system.cleanup.temp") * @Route("/moderation/system/cleanup/temp", name="moderation.system.cleanup.temp")
*/ */
......
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use App\Entity\UserNotification;
use App\Entity\Song;
use App\Entity\User;
use App\Entity\Promo;
class NotificationController extends AbstractController
{
/**
* @Route("/notification/clear/{notificationID}", name="notification.clear")
*/
public function clearNotification(Request $request, $notificationID)
{
$em = $this->getDoctrine()->getManager();
$notificationToClear = $em->getRepository(UserNotification::class)->findOneBy(array('id' => $notificationID, 'user' => $this->getUser()));
$em->remove($notificationToClear);
$em->flush();
switch($notificationToClear->getNotificationType()) {
default:
case 0:
// System
$returnUrl = $request->query->get('returnUrl');
return $this->redirect($returnUrl);
break;
case 1:
// Review
return $this->redirectToRoute('song.detail', ['songId' => $notificationToClear->getConnectedSong()->getId(), 'tab' => 'reviews']);
break;
case 2:
// SpinPlay
return $this->redirectToRoute('song.detail', ['songId' => $notificationToClear->getConnectedSong()->getId(), 'tab' => 'spinplays']);
break;
}
}
/**
* @Route("/notification/clearAll", name="notification.clear.all")
*/
public function clearAllNotifications(Request $request)
{
$em = $this->getDoctrine()->getManager();
$notificationsToClear = $em->getRepository(UserNotification::class)->findBy(array('user' => $this->getUser()));
foreach($notificationsToClear as $notification) {
$em->remove($notification);
}
$em->flush();
// Return to the previous URL
$returnUrl = $request->query->get('returnUrl');
return $this->redirect($returnUrl);
}
}
...@@ -20,6 +20,7 @@ use App\Entity\SongReview; ...@@ -20,6 +20,7 @@ use App\Entity\SongReview;
use App\Entity\SongSpinPlay; use App\Entity\SongSpinPlay;
use App\Utils\HelperFunctions; use App\Utils\HelperFunctions;
use App\Entity\User; use App\Entity\User;
use App\Entity\UserNotification;
class SongController extends AbstractController class SongController extends AbstractController
{ {
...@@ -70,6 +71,15 @@ class SongController extends AbstractController ...@@ -70,6 +71,15 @@ class SongController extends AbstractController
$em->persist($newReview); $em->persist($newReview);
$em->flush(); $em->flush();
$newNotification = new UserNotification();
$newNotification->setUser($resultUploader);
$newNotification->setConnectedUser($this->getUser());
$newNotification->setConnectedSong($resultSong);
$newNotification->setNotificationType(1);
$newNotification->setNotificationData($newReview->getID());
$em->persist($newNotification);
$em->flush();
return $this->redirectToRoute('song.detail', ['songId' => $resultSong->getId(), 'tab' => 'reviews']); return $this->redirectToRoute('song.detail', ['songId' => $resultSong->getId(), 'tab' => 'reviews']);
} }
} }
...@@ -85,6 +95,15 @@ class SongController extends AbstractController ...@@ -85,6 +95,15 @@ class SongController extends AbstractController
$em->persist($newSpinPlay); $em->persist($newSpinPlay);
$em->flush(); $em->flush();
$newNotification = new UserNotification();
$newNotification->setUser($resultUploader);
$newNotification->setConnectedUser($this->getUser());
$newNotification->setConnectedSong($resultSong);
$newNotification->setNotificationType(2);
$newNotification->setNotificationData($newSpinPlay->getID());
$em->persist($newNotification);
$em->flush();
return $this->redirectToRoute('song.detail', ['songId' => $resultSong->getId(), 'tab' => 'spinplays']); return $this->redirectToRoute('song.detail', ['songId' => $resultSong->getId(), 'tab' => 'spinplays']);
} }
} else { } else {
......
...@@ -10,6 +10,7 @@ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; ...@@ -10,6 +10,7 @@ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use FOS\UserBundle\Model\UserManagerInterface; use FOS\UserBundle\Model\UserManagerInterface;
use App\Entity\User; use App\Entity\User;
use App\Entity\Connection;
class UserSettingsController extends AbstractController class UserSettingsController extends AbstractController
{ {
...@@ -75,14 +76,28 @@ class UserSettingsController extends AbstractController ...@@ -75,14 +76,28 @@ class UserSettingsController extends AbstractController
} }
/** /**
* @Route("/settings/login", name="user.settings.login") * @Route("/settings/connect", name="user.settings.connect")
*/ */
public function userSettingsLogin(Request $request) public function userSettingsConnect(Request $request)
{
$em = $this->getDoctrine()->getManager();
$data = [];
return $this->render('user/settings/connect.html.twig', $data);
}
/**
* @Route("/settings/connect/revoke/{connectionID}", name="user.settings.connect.revoke")
*/
public function userSettingsConnectRemove(Request $request, $connectionID)
{ {
$user = $this->getUser();
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$data = []; $data = [];
return $this->render('user/settings/login.html.twig', $data); $connectionToRevoke = $em->getRepository(Connection::class)->findOneBy(array('id' => $connectionID));
$em->remove($connectionToRevoke);
$em->flush();
return $this->redirectToRoute('user.settings.connect');
} }
} }
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\ConnectAppRepository")
*/
class ConnectApp
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $title;
/**
* @ORM\Column(type="string", length=255)
*/
private $apiKey;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Connection", mappedBy="app")
*/
private $connections;
public function __construct()
{
$this->connections = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getApiKey(): ?string
{
return $this->apiKey;
}
public function setApiKey(string $apiKey): self
{
$this->apiKey = $apiKey;
return $this;
}
/**
* @return Collection|Connection[]
*/
public function getConnections(): Collection
{
return $this->connections;
}
public function addConnection(Connection $connection): self
{
if (!$this->connections->contains($connection)) {
$this->connections[] = $connection;
$connection->setApp($this);
}
return $this;
}
public function removeConnection(Connection $connection): self
{
if ($this->connections->contains($connection)) {
$this->connections->removeElement($connection);
// set the owning side to null (unless already changed)
if ($connection->getApp() === $this) {
$connection->setApp(null);
}
}
return $this;
}
}
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\ConnectionRepository")
*/
class Connection
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="connections")
* @ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* @ORM\Column(type="datetime")
*/
private $connectDate;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\ConnectApp", inversedBy="connections")
*/
private $app;
/**
* @ORM\Column(type="string", length=255)
*/
private $connectToken;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getConnectDate(): ?\DateTimeInterface
{
return $this->connectDate;
}
public function setConnectDate(\DateTimeInterface $connectDate): self
{
$this->connectDate = $connectDate;
return $this;
}
public function getApp(): ?ConnectApp
{
return $this->app;
}
public function setApp(?ConnectApp $app): self
{
$this->app = $app;
return $this;
}
public function getConnectToken(): ?string
{
return $this->connectToken;
}
public function setConnectToken(string $connectToken): self
{
$this->connectToken = $connectToken;
return $this;
}
}
...@@ -46,13 +46,30 @@ ...@@ -46,13 +46,30 @@
*/ */
private $spinPlays; private $spinPlays;
/**
* @ORM\Column(type="string", length=6, nullable=true)
*/
private $connectCode;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Connection", mappedBy="user")
*/
private $connections;
/**
* @ORM\OneToMany(targetEntity="App\Entity\UserNotification", mappedBy="user")
*/
private $userNotifications;
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->reviews = new ArrayCollection(); $this->reviews = new ArrayCollection();
$this->spinPlays = new ArrayCollection(); $this->spinPlays = new ArrayCollection();
// your own logic // your own logic
}
$this->connections = new ArrayCollection();
$this->userNotifications = new ArrayCollection(); }
public function getCoverReference(): ?string public function getCoverReference(): ?string
{ {
...@@ -160,4 +177,78 @@ ...@@ -160,4 +177,78 @@
return $response; return $response;
} }
public function getConnectCode(): ?string
{
return $this->connectCode;
}
public function setConnectCode(?string $connectCode): self
{
$this->connectCode = $connectCode;
return $this;
}
/**
* @return Collection|Connection[]
*/
public function getConnections(): Collection
{
return $this->connections;
}
public function addConnection(Connection $connection): self
{
if (!$this->connections->contains($connection)) {
$this->connections[] = $connection;
$connection->setUser($this);
}
return $this;
}
public function removeConnection(Connection $connection): self
{
if ($this->connections->contains($connection)) {
$this->connections->removeElement($connection);
// set the owning side to null (unless already changed)
if ($connection->getUser() === $this) {
$connection->setUser(null);
}
}
return $this;
}
/**
* @return Collection|UserNotification[]
*/
public function getUserNotifications(): Collection
{
return $this->userNotifications;
}
public function addUserNotification(UserNotification $userNotification): self
{
if (!$this->userNotifications->contains($userNotification)) {
$this->userNotifications[] = $userNotification;
$userNotification->setUser($this);
}
return $this;
}
public function removeUserNotification(UserNotification $userNotification): self
{
if ($this->userNotifications->contains($userNotification)) {
$this->userNotifications->removeElement($userNotification);
// set the owning side to null (unless already changed)
if ($userNotification->getUser() === $this) {
$userNotification->setUser(null);
}
}
return $this;
}
} }
\ No newline at end of file
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\UserNotificationRepository")
*/
class UserNotification
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="userNotifications")
*/
private $user;
/**
* @ORM\Column(type="integer")
*/
private $notificationType;
/**
* @ORM\Column(type="string", length=255)
*/
private $notificationData;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Song")
*/
private $connectedSong;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\User")
*/
private $connectedUser;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getNotificationType(): ?int
{
return $this->notificationType;
}
public function setNotificationType(int $notificationType): self
{
$this->notificationType = $notificationType;
return $this;
}
public function getNotificationData(): ?string
{
return $this->notificationData;
}
public function setNotificationData(string $notificationData): self
{
$this->notificationData = $notificationData;
return $this;
}
public function getConnectedSong(): ?Song
{
return $this->connectedSong;
}
public function setConnectedSong(?Song $connectedSong): self
{
$this->connectedSong = $connectedSong;
return $this;
}
public function getConnectedUser(): ?User
{
return $this->connectedUser;
}
public function setConnectedUser(?User $connectedUser): self
{
$this->connectedUser = $connectedUser;
return $this;
}
}
<?php
namespace App\Repository;
use App\Entity\ConnectApp;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ConnectApp|null find($id, $lockMode = null, $lockVersion = null)
* @method ConnectApp|null findOneBy(array $criteria, array $orderBy = null)
* @method ConnectApp[] findAll()
* @method ConnectApp[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ConnectAppRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ConnectApp::class);
}
// /**
// * @return ConnectApp[] Returns an array of ConnectApp objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('c')
->andWhere('c.exampleField = :val')
->setParameter('val', $value)
->orderBy('c.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?ConnectApp
{
return $this->createQueryBuilder('c')
->andWhere('c.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}
<?php
namespace App\Repository;
use App\Entity\Connection;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Connection|null find($id, $lockMode = null, $lockVersion = null)
* @method Connection|null findOneBy(array $criteria, array $orderBy = null)
* @method Connection[] findAll()
* @method Connection[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ConnectionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Connection::class);
}
}
<?php
namespace App\Repository;
use App\Entity\UserNotification;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method UserNotification|null find($id, $lockMode = null, $lockVersion = null)
* @method UserNotification|null findOneBy(array $criteria, array $orderBy = null)
* @method UserNotification[] findAll()
* @method UserNotification[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserNotificationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, UserNotification::class);
}
// /**
// * @return UserNotification[] Returns an array of UserNotification objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->orderBy('u.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?UserNotification
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}
...@@ -43,43 +43,41 @@ ...@@ -43,43 +43,41 @@
<div class="upload-button"> <div class="upload-button">
<a href="{{ path('upload.index') }}" class="button">Upload</a> <a href="{{ path('upload.index') }}" class="button">Upload</a>
</div> </div>
{# <div class="item item-notifications" tabindex="0"> <div class="item item-notifications" tabindex="0">
<i class="mdi mdi-bell"></i> <i class="mdi mdi-bell"></i>
<div class="indicator">2</div> {% if app.user.userNotifications|length > 0 %}
<div class="indicator">{{ app.user.userNotifications|length }}</div>
{% endif %}
<div class="notification-box"> <div class="notification-box">
<header> <header>
<span>Notifications</span> <span>Notifications</span>
<div class="button">Clear all</div> <a href="{{ path('notification.clear.all', {returnUrl: app.request.uri}) }}" class="button">Clear all</a>
</header> </header>
<div class="notification-list"> <div class="notification-list">
<a href="{{ path('index.index') }}" class="notification-item"> {% for notification in app.user.userNotifications|reverse %}
<a href="{{ path('notification.clear', {notificationID: notification.id, returnUrl: app.request.uri}) }}" class="notification-item">
<div class="notification-icon"> <div class="notification-icon">
{% if notification.notificationType == 0 %}
<i class="mdi mdi-server"></i> <i class="mdi mdi-server"></i>
{% else %}
<div class="cover" style="background-image: url({{ asset("uploads/thumbnail/" ~ notification.connectedSong.fileReference ~ ".jpg?v=" ~ date().timestamp) }}), url({{ asset("assets/img/defaultAlbumArt.jpg") }});"></div>
{% endif %}
</div> </div>
<div class="notification-text"> <div class="notification-text">
Hello! This is your first notification! {% if notification.notificationType == 0 %}
</div> {{ notification.notificationData }}
</a> {% elseif notification.notificationType == 1 %}
<a href="{{ path('index.index') }}" class="notification-item"> <strong>{{ notification.connectedUser.username }}</strong> reviewed your chart <strong>{{ notification.connectedSong.title }}</strong>.
<div class="notification-icon"> {% elseif notification.notificationType == 2 %}
<div class="cover" style="background-image: url(/www/spinshare/server/public/uploads/thumbnail/spinshare_5ea59d9ff2be9.jpg?v=1595247910), url(/www/spinshare/server/public/assets/img/defaultAlbumArt.jpg);"></div> <strong>{{ notification.connectedUser.username }}</strong> added a SpinPlay to your chart <strong>{{ notification.connectedSong.title }}</strong>.
</div> {% endif %}
<div class="notification-text">
<strong>taw</strong> reviewed your chart <strong>Xenoblade Chronicles Medley (Tournament Edit)</strong>.
</div> </div>
</a> </a>
<a href="{{ path('index.index') }}" class="notification-item"> {% endfor %}
<div class="notification-icon">
<div class="cover" style="background-image: url(/www/spinshare/server/public/uploads/thumbnail/spinshare_5ea59d9ff2be9.jpg?v=1595247910), url(/www/spinshare/server/public/assets/img/defaultAlbumArt.jpg);"></div>
</div> </div>
<div class="notification-text">
<strong>taw</strong> added a SpinPlay to your chart <strong>Xenoblade Chronicles Medley (Tournament Edit)</strong>.
</div>
</a>
</div> </div>
</div> </div>
</div> #}
<div class="item item-user" tabindex="0"> <div class="item item-user" tabindex="0">
<div class="user-avatar" style="background-image: url({{ asset('uploads/avatar/' ~ app.user.coverReference ~ '?t=' ~ date().timestamp) }}), url({{ asset("assets/img/defaultAvatar.jpg") }});"></div> <div class="user-avatar" style="background-image: url({{ asset('uploads/avatar/' ~ app.user.coverReference ~ '?t=' ~ date().timestamp) }}), url({{ asset("assets/img/defaultAvatar.jpg") }});"></div>
<div class="user-actions"> <div class="user-actions">
......
...@@ -8,6 +8,13 @@ ...@@ -8,6 +8,13 @@
<div class="flashbang flashbang-error">{{ app.session.flashbag.get('message') }}</div> <div class="flashbang flashbang-error">{{ app.session.flashbag.get('message') }}</div>
{% endif %} {% endif %}
<div class="box">
<form action="{{ path('moderation.system.notificationToEveryone') }}" method="post">
<input type="text" value="" name="notificationData" size="150" />
<input type="submit" value="Send to everyone!" />
</form>
</div>
<div class="box"> <div class="box">
Generate On All Songs Actions<br /><strong>(ATTENTION: They can take a while!)</strong><br /><br /> Generate On All Songs Actions<br /><strong>(ATTENTION: They can take a while!)</strong><br /><br />
<a href="{{ path('moderation.system.generate.thumbnails') }}" class="button">Generate Thumbnails</a> <a href="{{ path('moderation.system.generate.thumbnails-missing') }}" class="button">Generate Missing Thumbnails</a><br /><br /> <a href="{{ path('moderation.system.generate.thumbnails') }}" class="button">Generate Thumbnails</a> <a href="{{ path('moderation.system.generate.thumbnails-missing') }}" class="button">Generate Missing Thumbnails</a><br /><br />
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
{% block content %} {% block content %}
<section class="section-settings"> <section class="section-settings">
<header> <header>
<div class="title">Settings</div> <div class="title">{{ sectionTitle }}</div>
<div class="tabs"> <div class="tabs">
<a class="tab {% if app.request.attributes.get('_route') == 'user.settings' %}active{% endif %}" href="{{ path('user.settings') }}">General</a> <a class="tab {% if app.request.attributes.get('_route') == 'user.settings' %}active{% endif %}" href="{{ path('user.settings') }}">General</a>
<a class="tab {% if app.request.attributes.get('_route') == 'user.settings.security' %}active{% endif %}" href="{{ path('user.settings.security') }}">Security</a> <a class="tab {% if app.request.attributes.get('_route') == 'user.settings.security' %}active{% endif %}" href="{{ path('user.settings.security') }}">Security</a>
<a class="tab {% if app.request.attributes.get('_route') == 'user.settings.login' %}active{% endif %}" href="{{ path('user.settings.login') }}">Login</a> <a class="tab {% if app.request.attributes.get('_route') == 'user.settings.connect' %}active{% endif %}" href="{{ path('user.settings.connect') }}">Connect</a>
</div> </div>
</header> </header>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% block settings %} {% block settingsContent %}
{% endblock %} {% endblock %}
</section> </section>
{% endblock %} {% endblock %}
......
{% extends 'user/settings/base.html.twig' %}
{% set sectionTitle = 'Connect' %}
{% block settingsContent %}
<section class="section-connect">
<div class="connect-content">
<div class="connect-title">Connect Code</div>
<div class="connect-code">&nbsp;</div>
<div class="connect-timeout">
<div class="connect-progress"></div>
</div>
<div class="connect-explaination">Use this code to log into third party applications or the official SpinShare client.</div>
</div>
<div class="connect-connections">
<div class="connect-title">Connections</div>
<div class="connections-list">
{% if app.user.connections|length == 0 %}
<div class="noconnection-item">
<div class="noconnection-title">No connections yet</div>
<div class="noconnection-connected">You haven't connected your SpinShare account to any app yet.</div>
</div>
{% endif %}
{% for connection in app.user.connections %}
<div class="connection-item">
<div class="connection-title">{{ connection.app.title }}</div>
<div class="connection-connected">Connected on {{ connection.connectDate|date("m/d/Y") }}</div>
<a href="{{ path('user.settings.connect.revoke', {connectionID: connection.id}) }}" class="button">Revoke Access</a>
</div>
{% endfor %}
</div>
</div>
</section>
{% endblock %}
{% block scripts %}
<script>
let DOMConnectCode = document.querySelector(".connect-code");
let DOMConnectTimeoutProgress = document.querySelector(".connect-timeout .connect-progress");
function generateCode() {
fetch('{{ path('api.connect.generateCode') }}')
.then(response => response.json())
.then(data => {
if(data.status == 200) {
DOMConnectCode.innerText = data.data;
DOMConnectTimeoutProgress.style.animation = "none";
setTimeout(() => { DOMConnectTimeoutProgress.style.animation = "timeoutProgress 15s linear"; }, 100);
} else {
DOMConnectCode.innerText = "";
DOMConnectTimeoutProgress.classList.remove("active");
}
});
}
generateCode();
setInterval(() => { generateCode(); }, 15000);
</script>
{% endblock %}
\ No newline at end of file
{% extends 'user/settings/base.html.twig' %} {% extends 'user/settings/base.html.twig' %}
{% block settings %} {% set sectionTitle = 'General Settings' %}
{% block settingsContent %}
<form action="" method="POST" class="settings-box"> <form action="" method="POST" class="settings-box">
<div class="settings-title">General</div> <div class="settings-title">General</div>
<div class="settings-item"> <div class="settings-item">
......
{% extends 'user/settings/base.html.twig' %}
{% block settings %}
<div class="coming-soon">
<img src="{{ asset('assets/img/illustration-login.svg') }}" alt="Illustration" />
<div class="title">Coming Soon</div>
</div>
{% endblock %}
\ No newline at end of file
{% extends 'user/settings/base.html.twig' %} {% extends 'user/settings/base.html.twig' %}
{% block settings %} {% set sectionTitle = 'Security Settings' %}
{% block settingsContent %}
<form action="" method="POST" class="settings-box"> <form action="" method="POST" class="settings-box">
<div class="settings-title">Login</div> <div class="settings-title">Login</div>
<div class="settings-item"> <div class="settings-item">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment