add projet
This commit is contained in:
+621
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use App\Models\Project;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use App\Models\TeacherClass;
|
||||
use App\Services\SemesterScoreService;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ProjectController extends BaseApiController
|
||||
{
|
||||
protected Project $project;
|
||||
protected Configuration $config;
|
||||
protected Student $student;
|
||||
protected StudentClass $studentClass;
|
||||
protected TeacherClass $teacherClass;
|
||||
protected SemesterScoreService $semesterScoreService;
|
||||
protected string $schoolYear;
|
||||
protected string $semester;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->project = model(Project::class);
|
||||
$this->config = model(Configuration::class);
|
||||
$this->student = model(Student::class);
|
||||
$this->studentClass = model(StudentClass::class);
|
||||
$this->teacherClass = model(TeacherClass::class);
|
||||
$this->semesterScoreService = app(SemesterScoreService::class);
|
||||
|
||||
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
||||
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/projects
|
||||
* List projects with optional filters
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
||||
$perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 20)));
|
||||
$studentId = $this->request->getGet('student_id');
|
||||
$classSectionId = $this->request->getGet('class_section_id');
|
||||
|
||||
$query = $this->project->newQuery()
|
||||
->where('school_year', $this->schoolYear)
|
||||
->where('semester', $this->semester);
|
||||
|
||||
if ($studentId) {
|
||||
$query->where('student_id', $studentId);
|
||||
}
|
||||
|
||||
if ($classSectionId) {
|
||||
$query->where('class_section_id', $classSectionId);
|
||||
}
|
||||
|
||||
$result = $this->paginate($query, $page, $perPage);
|
||||
|
||||
return $this->success($result, 'Projects retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/projects/{id}
|
||||
* Get a single project
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
$project = $this->project->find($id);
|
||||
if (!$project) {
|
||||
return $this->error('Project not found', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->success($project, 'Project retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/projects/student/{id}
|
||||
* Get projects for a specific student
|
||||
*/
|
||||
public function getByStudent($id = null)
|
||||
{
|
||||
$projects = $this->project->newQuery()
|
||||
->where('student_id', $id)
|
||||
->where('school_year', $this->schoolYear)
|
||||
->where('semester', $this->semester)
|
||||
->orderBy('project_index', 'ASC')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
return $this->success($projects, 'Projects retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/projects
|
||||
* Create a new project entry
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user) {
|
||||
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$data = $this->payloadData();
|
||||
if (empty($data)) {
|
||||
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'student_id' => 'required|integer',
|
||||
'class_section_id' => 'required|integer',
|
||||
'project_index' => 'required|integer',
|
||||
];
|
||||
|
||||
$errors = $this->validateRequest($data, $rules);
|
||||
if (!empty($errors)) {
|
||||
return $this->respondValidationError($errors);
|
||||
}
|
||||
|
||||
$projectData = [
|
||||
'student_id' => $data['student_id'],
|
||||
'class_section_id' => $data['class_section_id'],
|
||||
'project_index' => $data['project_index'],
|
||||
'score' => $data['score'] ?? null,
|
||||
'comment' => $data['comment'] ?? null,
|
||||
'school_year' => $this->schoolYear,
|
||||
'semester' => $this->semester,
|
||||
'updated_by' => $user->id,
|
||||
];
|
||||
|
||||
try {
|
||||
$project = $this->project->create($projectData);
|
||||
return $this->success($project->toArray(), 'Project created successfully', Response::HTTP_CREATED);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Project creation error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to create project', Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/v1/projects/{id}
|
||||
* Update a project entry
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
$project = $this->project->find($id);
|
||||
if (!$project) {
|
||||
return $this->error('Project not found', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user) {
|
||||
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$data = $this->payloadData();
|
||||
if (empty($data)) {
|
||||
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$allowedFields = ['score', 'comment', 'project_index'];
|
||||
$updateData = [];
|
||||
foreach ($allowedFields as $field) {
|
||||
if (array_key_exists($field, $data)) {
|
||||
$updateData[$field] = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->error('No valid fields to update', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$updateData['updated_by'] = $user->id;
|
||||
$updateData['updated_at'] = utc_now();
|
||||
|
||||
try {
|
||||
$this->project->update($id, $updateData);
|
||||
$updatedProject = $this->project->find($id);
|
||||
return $this->success($updatedProject, 'Project updated successfully');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Project update error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to update project', Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/projects/add-project
|
||||
* Get project data for teacher's class section (equivalent to addProject view)
|
||||
*/
|
||||
public function addProject()
|
||||
{
|
||||
$updatedBy = $this->getCurrentUserId();
|
||||
if (!$updatedBy) {
|
||||
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$teacherClass = $this->teacherClass->where('teacher_id', $updatedBy)->first();
|
||||
if (!$teacherClass) {
|
||||
return $this->respondError('No class section found for the current teacher.', Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$classSectionId = $teacherClass['class_section_id'];
|
||||
session()->put('class_section_id', $classSectionId);
|
||||
|
||||
$projectRows = $this->project
|
||||
->select('id, project_index')
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('updated_by', $updatedBy)
|
||||
->where('semester', $this->semester)
|
||||
->where('school_year', $this->schoolYear)
|
||||
->orderBy('project_index', 'ASC')
|
||||
->findAll();
|
||||
|
||||
$projectHeaders = [];
|
||||
foreach ($projectRows as $row) {
|
||||
$projectHeaders[$row['project_index']][] = $row['id'];
|
||||
}
|
||||
|
||||
$headerLabels = array_keys($projectHeaders);
|
||||
|
||||
$studentsClasses = $this->studentClass->where('class_section_id', $classSectionId)->findAll();
|
||||
$students = [];
|
||||
|
||||
foreach ($studentsClasses as $studentClass) {
|
||||
$studentId = $studentClass['student_id'];
|
||||
$student = $this->student->find($studentId);
|
||||
if ($student) {
|
||||
$scores = [];
|
||||
foreach ($projectHeaders as $index => $ids) {
|
||||
$entry = $this->project
|
||||
->where('student_id', $studentId)
|
||||
->where('project_index', $index)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('semester', $this->semester)
|
||||
->first();
|
||||
|
||||
$scores[$index] = $entry['score'] ?? '';
|
||||
}
|
||||
|
||||
$students[] = [
|
||||
'student_id' => $studentId,
|
||||
'firstname' => $student['firstname'],
|
||||
'lastname' => $student['lastname'],
|
||||
'scores' => $scores
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
usort($students, fn($a, $b) => strcmp($a['lastname'], $b['lastname']) ?: strcmp($a['firstname'], $b['firstname']));
|
||||
|
||||
return $this->success([
|
||||
'students' => $students,
|
||||
'project_headers' => $headerLabels,
|
||||
'semester' => $this->semester,
|
||||
'school_year' => $this->schoolYear,
|
||||
'class_section_id' => $classSectionId
|
||||
], 'Project data retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/projects/update-scores
|
||||
* Update project scores for multiple students
|
||||
*/
|
||||
public function updateProjectScores()
|
||||
{
|
||||
$updatedBy = $this->getCurrentUserId();
|
||||
if (!$updatedBy) {
|
||||
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$payload = $this->payloadData();
|
||||
$scores = $payload['scores'] ?? null;
|
||||
$classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0);
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!is_array($scores)) {
|
||||
return $this->respondError('No project scores submitted.', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($scores as $studentId => $projects) {
|
||||
if (!is_numeric($studentId)) continue;
|
||||
|
||||
$student = $this->student->find($studentId);
|
||||
if (!$student) continue;
|
||||
|
||||
foreach ($projects as $index => $score) {
|
||||
if (!is_numeric($score)) continue;
|
||||
|
||||
$existing = $this->project->where([
|
||||
'student_id' => $studentId,
|
||||
'project_index' => $index,
|
||||
'class_section_id' => $classSectionId,
|
||||
'semester' => $this->semester
|
||||
])->first();
|
||||
|
||||
$data = [
|
||||
'student_id' => $studentId,
|
||||
'school_id' => $student['school_id'] ?? '',
|
||||
'class_section_id' => $classSectionId,
|
||||
'updated_by' => $updatedBy,
|
||||
'project_index' => $index,
|
||||
'score' => is_numeric($score) ? (float)$score : null,
|
||||
'semester' => $this->semester,
|
||||
'school_year' => $this->schoolYear,
|
||||
'updated_at' => utc_now(),
|
||||
];
|
||||
|
||||
if ($existing) {
|
||||
$this->project->update($existing['id'], $data);
|
||||
} else {
|
||||
$data['created_at'] = utc_now();
|
||||
$this->project->insert($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$studentTeacherInfo = $this->student->getStudentInfoByClassSectionId($classSectionId);
|
||||
|
||||
// Call the updateScoresForStudents method
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentTeacherInfo);
|
||||
} catch (RuntimeException $e) {
|
||||
log_message('error', 'Failed to update semester scores: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return $this->success(null, 'Project scores updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Update project scores error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to update project scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/projects/add-column
|
||||
* Add a new project column for all students in a class section
|
||||
*/
|
||||
public function addNextProjectColumn()
|
||||
{
|
||||
$updatedBy = $this->getCurrentUserId();
|
||||
if (!$updatedBy) {
|
||||
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$payload = $this->payloadData();
|
||||
$classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0);
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
$existingIndexes = $this->project
|
||||
->select('project_index')
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('updated_by', $updatedBy)
|
||||
->where('semester', $this->semester)
|
||||
->where('school_year', $this->schoolYear)
|
||||
->groupBy('project_index')
|
||||
->orderBy('project_index', 'DESC')
|
||||
->findAll();
|
||||
|
||||
$maxIndex = 0;
|
||||
foreach ($existingIndexes as $row) {
|
||||
if (isset($row['project_index']) && is_numeric($row['project_index'])) {
|
||||
$maxIndex = max($maxIndex, (int)$row['project_index']);
|
||||
}
|
||||
}
|
||||
|
||||
$nextIndex = $maxIndex + 1;
|
||||
|
||||
$students = $this->studentClass->where('class_section_id', $classSectionId)->findAll();
|
||||
|
||||
$insertedCount = 0;
|
||||
foreach ($students as $student) {
|
||||
$exists = $this->project->where([
|
||||
'student_id' => $student['student_id'],
|
||||
'project_index' => $nextIndex,
|
||||
'class_section_id' => $classSectionId,
|
||||
'semester' => $this->semester,
|
||||
'school_year' => $this->schoolYear,
|
||||
])->first();
|
||||
|
||||
if (!$exists) {
|
||||
$studentRecord = $this->student->find($student['student_id']);
|
||||
$this->project->insert([
|
||||
'student_id' => $student['student_id'],
|
||||
'school_id' => $studentRecord['school_id'] ?? '',
|
||||
'class_section_id' => $classSectionId,
|
||||
'updated_by' => $updatedBy,
|
||||
'project_index' => $nextIndex,
|
||||
'score' => null,
|
||||
'semester' => $this->semester,
|
||||
'school_year' => $this->schoolYear,
|
||||
'created_at' => utc_now(),
|
||||
'updated_at' => utc_now()
|
||||
]);
|
||||
$insertedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'status' => 'success',
|
||||
'project_index' => $nextIndex,
|
||||
'students_processed' => $insertedCount
|
||||
], 'Project column added successfully');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Add project column error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to add project column: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/projects/management
|
||||
* Get project management data for a class section
|
||||
*/
|
||||
public function showProjectMngt()
|
||||
{
|
||||
// Accept POST first, then GET (query string), then session fallback
|
||||
$incomingId = $this->request->getPost('class_section_id');
|
||||
if (!$incomingId) {
|
||||
$incomingId = $this->request->getGet('class_section_id');
|
||||
}
|
||||
|
||||
$classSectionId = (int) ($incomingId ?: (session()->get('class_section_id') ?? 0));
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return $this->respondError('No class section found for the current teacher or selection.', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
session()->put('class_section_id', $classSectionId);
|
||||
|
||||
$projectScores = $this->getProjectScoresByStudent($classSectionId, $this->semester, $this->schoolYear);
|
||||
$students = $this->getStudentsByClassSectionAndYear($classSectionId, $this->schoolYear);
|
||||
$projectHeaders = $this->getProjectHeaders($classSectionId, $this->semester, $this->schoolYear);
|
||||
|
||||
return $this->success([
|
||||
'project_scores' => $projectScores,
|
||||
'students' => $students,
|
||||
'semester' => $this->semester,
|
||||
'school_year' => $this->schoolYear,
|
||||
'project_headers' => $projectHeaders,
|
||||
'class_section_id' => $classSectionId,
|
||||
], 'Project management data retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/projects/update
|
||||
* Update project scores (management endpoint)
|
||||
*/
|
||||
public function updateProject()
|
||||
{
|
||||
$payload = $this->payloadData();
|
||||
$scores = $payload['scores'] ?? null;
|
||||
$studentIds = $payload['student_ids'] ?? null;
|
||||
$semester = $payload['semester'] ?? $this->semester;
|
||||
$schoolYear = $payload['school_year'] ?? $this->schoolYear;
|
||||
$updatedBy = $payload['teacher_id'] ?? $this->getCurrentUserId();
|
||||
$classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0);
|
||||
|
||||
if (!$updatedBy) {
|
||||
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($classSectionId <= 0) {
|
||||
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if ($scores && $studentIds && $semester && $schoolYear) {
|
||||
try {
|
||||
$this->saveProjectScores($scores, $studentIds, $semester, $schoolYear, $updatedBy, $classSectionId);
|
||||
|
||||
$studentTeacherInfo = $this->student->getStudentInfoByClassSectionId($classSectionId);
|
||||
|
||||
// Call the updateScoresForStudents method
|
||||
try {
|
||||
$this->semesterScoreService->updateScoresForStudents($studentTeacherInfo);
|
||||
} catch (RuntimeException $e) {
|
||||
log_message('error', 'Failed to update semester scores: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return $this->success(null, 'Project scores updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Update project error: ' . $e->getMessage());
|
||||
return $this->respondError('Failed to update scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondError('Failed to update scores. Missing required data.', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Save project scores for multiple students
|
||||
*/
|
||||
private function saveProjectScores(array $scores, array $studentIds, string $semester, string $schoolYear, int $updatedBy, int $classSectionId)
|
||||
{
|
||||
$now = utc_now();
|
||||
|
||||
foreach ($studentIds as $studentId) {
|
||||
if (!isset($scores[$studentId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($scores[$studentId] as $index => $score) {
|
||||
// Check if the record exists
|
||||
$existing = $this->project
|
||||
->where('student_id', $studentId)
|
||||
->where('project_index', $index)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('updated_by', $updatedBy)
|
||||
->first();
|
||||
|
||||
$student = $this->student->find($studentId);
|
||||
$data = [
|
||||
'student_id' => $studentId,
|
||||
'class_section_id' => $classSectionId,
|
||||
'updated_by' => $updatedBy,
|
||||
'project_index' => $index,
|
||||
'score' => is_numeric($score) ? floatval($score) : null,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
if ($existing && isset($existing['id'])) {
|
||||
$this->project->update($existing['id'], $data);
|
||||
} else {
|
||||
$data['created_at'] = $now;
|
||||
$data['school_id'] = $student['school_id'] ?? '';
|
||||
$this->project->insert($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get project headers for a class section
|
||||
*/
|
||||
private function getProjectHeaders($classSectionId, $semester, $schoolYear)
|
||||
{
|
||||
$rows = $this->project
|
||||
->select('project_index')
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderBy('project_index', 'ASC')
|
||||
->findAll();
|
||||
|
||||
$headers = [];
|
||||
foreach ($rows as $row) {
|
||||
$index = $row['project_index'];
|
||||
if (!is_array($index)) {
|
||||
$headers[$index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($headers);
|
||||
return array_keys($headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get students by class section and year
|
||||
*/
|
||||
private function getStudentsByClassSectionAndYear($classSectionId, $schoolYear)
|
||||
{
|
||||
// Step 1: Get student IDs from student_class table
|
||||
$studentClassRows = $this->studentClass
|
||||
->select('student_id')
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('school_year', $schoolYear)
|
||||
->findAll();
|
||||
|
||||
$studentIds = array_column($studentClassRows, 'student_id');
|
||||
|
||||
if (empty($studentIds)) {
|
||||
return []; // No students found
|
||||
}
|
||||
|
||||
// Step 2: Get student data from students table
|
||||
$students = $this->student
|
||||
->whereIn('id', $studentIds)
|
||||
->orderBy('lastname', 'ASC')
|
||||
->findAll();
|
||||
|
||||
return $students;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get project scores by student
|
||||
*/
|
||||
private function getProjectScoresByStudent($classSectionId, $semester, $schoolYear)
|
||||
{
|
||||
$rows = $this->project
|
||||
->select('student_id, project_index, score')
|
||||
->where('class_section_id', $classSectionId)
|
||||
->where('semester', $semester)
|
||||
->where('school_year', $schoolYear)
|
||||
->findAll();
|
||||
|
||||
$studentScores = [];
|
||||
foreach ($rows as $row) {
|
||||
$studentId = $row['student_id'];
|
||||
$index = $row['project_index'];
|
||||
$score = $row['score'];
|
||||
|
||||
$studentScores[$studentId]['scores'][$index] = $score;
|
||||
}
|
||||
|
||||
return $studentScores;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user