feat(api): add endpoints to edit and delete cover letters
This commit is contained in:
@@ -142,8 +142,83 @@ func Post(c *gin.Context) {
|
|||||||
res.Success(c, gin.H{"message": "Successfully created " + coverName})
|
res.Success(c, gin.H{"message": "Successfully created " + coverName})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CoverPut struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1"`
|
||||||
|
Letter string `json:"letter" validate:"required,min=50"`
|
||||||
|
}
|
||||||
|
|
||||||
func Put(c *gin.Context) {
|
func Put(c *gin.Context) {
|
||||||
|
// Get request data
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var data CoverPut
|
||||||
|
if err := utils.BindAndValidate(&data, c); err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := jwt.GetUser(c)
|
||||||
|
if err != nil {
|
||||||
|
res.NeedsToLogin(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find cover letter in database, verify it exists, and update it
|
||||||
|
letters, err := cover.Get("user_id = $1 AND id = $2", user.Id, id)
|
||||||
|
if err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(letters) == 0 {
|
||||||
|
res.Error(c, "Cover letter not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cover.Update(data.Name, data.Letter, id)
|
||||||
|
if err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Success(c, gin.H{"message": "Successfully updated cover letter"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Delete(c *gin.Context) {
|
func Delete(c *gin.Context) {
|
||||||
|
// Get request data
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := jwt.GetUser(c)
|
||||||
|
if err != nil {
|
||||||
|
res.NeedsToLogin(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find cover letter in database, verify it exists, and delete it
|
||||||
|
letters, err := cover.Get("user_id = $1 AND id = $2", user.Id, id)
|
||||||
|
if err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(letters) == 0 {
|
||||||
|
res.Error(c, "Cover letter not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cover.Delete(id)
|
||||||
|
if err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Success(c, gin.H{"message": "Successfully deleted cover letter"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,3 +61,23 @@ func Create(name string, letter string, userId float64) error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Update(name string, letter string, id int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `UPDATE cover_letters SET name = $1, letter = $2 WHERE id = $3`
|
||||||
|
_, err := db.Pool.Exec(ctx, query, name, letter, id)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(id int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `DELETE FROM cover_letters WHERE id = $1`
|
||||||
|
_, err := db.Pool.Exec(ctx, query, id)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ func SetupRoutes() *gin.Engine {
|
|||||||
|
|
||||||
// Cover letter routes
|
// Cover letter routes
|
||||||
covers := auth.Group("/cover")
|
covers := auth.Group("/cover")
|
||||||
covers.GET("", cover.Get)
|
covers.GET("", cover.Get) // Get all letters
|
||||||
covers.GET("/:id", cover.GetID)
|
covers.GET("/:id", cover.GetID) // get single letter
|
||||||
covers.POST("", cover.Post)
|
covers.POST("", cover.Post) // create new letter
|
||||||
|
covers.PUT("/:id", cover.Put) // edit letter
|
||||||
|
covers.DELETE("/:id", cover.Delete) // delete letter
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import Authorised from "@/layouts/Authorised";
|
|||||||
import requests from "@/lib/requests";
|
import requests from "@/lib/requests";
|
||||||
import type { CoverLetter } from "@/types/api";
|
import type { CoverLetter } from "@/types/api";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
|
||||||
import "../../editor.css";
|
import "../../editor.css";
|
||||||
import { toPng } from "html-to-image";
|
import { toPng } from "html-to-image";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { DownloadIcon, EditIcon } from "lucide-react";
|
import { DownloadIcon, EditIcon, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
export const Route = createFileRoute("/cover/$coverId")({
|
export const Route = createFileRoute("/cover/$coverId")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
@@ -16,6 +16,7 @@ export const Route = createFileRoute("/cover/$coverId")({
|
|||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { coverId } = Route.useParams();
|
const { coverId } = Route.useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const cover = useQuery({
|
const cover = useQuery({
|
||||||
queryKey: ["cover", coverId],
|
queryKey: ["cover", coverId],
|
||||||
@@ -47,12 +48,30 @@ function RouteComponent() {
|
|||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
const a = confirm("Are you sure?");
|
||||||
|
if (!a) return;
|
||||||
|
|
||||||
|
requests.delete(`/cover/${coverId}`, {
|
||||||
|
success() {
|
||||||
|
navigate({ to: "/" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Authorised>
|
<Authorised>
|
||||||
<div className="flex items-center gap-4 mb-8 md:justify-between">
|
<div className="flex items-center gap-4 mb-8 md:justify-between">
|
||||||
<h1 className="text-2xl font-semibold">{cover.data?.cover.name || "Loading..."}</h1>
|
<h1 className="text-2xl font-semibold">{cover.data?.cover.name || "Loading..."}</h1>
|
||||||
|
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
|
<Button
|
||||||
|
className="hover:bg-danger hover:text-background"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
<Link
|
<Link
|
||||||
to={"/cover/edit/$coverId"}
|
to={"/cover/edit/$coverId"}
|
||||||
params={{ coverId: cover.data?.cover.id.toString() || "" }}
|
params={{ coverId: cover.data?.cover.id.toString() || "" }}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function RouteComponent() {
|
|||||||
onBlur: editSchema,
|
onBlur: editSchema,
|
||||||
},
|
},
|
||||||
onSubmit({ value }) {
|
onSubmit({ value }) {
|
||||||
requests.post(`/cover/${coverId}`, {
|
requests.put(`/cover/${coverId}`, {
|
||||||
data: value,
|
data: value,
|
||||||
before() {
|
before() {
|
||||||
loading[1](true);
|
loading[1](true);
|
||||||
|
|||||||
Reference in New Issue
Block a user