checkpoint(cover): backend ready before AI development
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
|
# API key for chatgpt
|
||||||
|
CHATGPT_KEY=api key for chatgpt
|
||||||
# This is secret key for jwt signature
|
# This is secret key for jwt signature
|
||||||
JWT_SECRET=just a random string here
|
JWT_SECRET=just a random string here
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
data
|
data
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ func LoadEnv() {
|
|||||||
Env["db"] = defaultValue(os.Getenv("POSTGRES_DB"), "postgresql://postgres:postgres@db:5432/cover-letter")
|
Env["db"] = defaultValue(os.Getenv("POSTGRES_DB"), "postgresql://postgres:postgres@db:5432/cover-letter")
|
||||||
Env["JWT_SECRET"] = defaultValue(os.Getenv("JWT_SECRET"), "just a random string here")
|
Env["JWT_SECRET"] = defaultValue(os.Getenv("JWT_SECRET"), "just a random string here")
|
||||||
Env["Environment"] = defaultValue(os.Getenv("Environment"), "dev")
|
Env["Environment"] = defaultValue(os.Getenv("Environment"), "dev")
|
||||||
|
Env["CHATGPT_KEY"] = defaultValue(os.Getenv("CHATGPT_KEY"), "")
|
||||||
}
|
}
|
||||||
|
|||||||
55
backend/controllers/cover/cover.go
Normal file
55
backend/controllers/cover/cover.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package cover
|
||||||
|
|
||||||
|
import (
|
||||||
|
"backend/models/template"
|
||||||
|
"backend/utils"
|
||||||
|
"backend/utils/jwt"
|
||||||
|
res "backend/utils/responses"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validate = validator.New()
|
||||||
|
|
||||||
|
func Get(c *gin.Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoverPost struct {
|
||||||
|
TemplateId string `json:"templateId" validate:"required,number,min=1"`
|
||||||
|
Application string `json:"application" validate:"required,min=50"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(c *gin.Context) {
|
||||||
|
// Receive data from frontend, check if data is okay, hash password, call model
|
||||||
|
var data CoverPost
|
||||||
|
if err := utils.BindAndValidate(&data, c); err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user data from the token
|
||||||
|
user, err := jwt.GetUser(c)
|
||||||
|
if err != nil {
|
||||||
|
res.NeedsToLogin(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tempalte
|
||||||
|
templates, err := template.Get("user_id = $1 AND id = $2", user.Id, data.TemplateId)
|
||||||
|
if err != nil {
|
||||||
|
res.Error(c, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call chat and ask for cover letter nicely
|
||||||
|
|
||||||
|
res.Success(c, templates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Put(c *gin.Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(c *gin.Context) {
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"backend/controllers/cover"
|
||||||
"backend/controllers/template"
|
"backend/controllers/template"
|
||||||
"backend/controllers/user"
|
"backend/controllers/user"
|
||||||
"backend/middleware"
|
"backend/middleware"
|
||||||
@@ -29,5 +30,9 @@ func SetupRoutes() *gin.Engine {
|
|||||||
// PUT (Edit)
|
// PUT (Edit)
|
||||||
// DELETE (Delete)
|
// DELETE (Delete)
|
||||||
|
|
||||||
|
// Cover letter routes
|
||||||
|
covers := auth.Group("/cover")
|
||||||
|
covers.POST("", cover.Post)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
1
backend/utils/chatgpt/chatgpt.go
Normal file
1
backend/utils/chatgpt/chatgpt.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package chatgpt
|
||||||
29
backend/utils/checkData.go
Normal file
29
backend/utils/checkData.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validate = validator.New()
|
||||||
|
|
||||||
|
func BindAndValidate(data any, c *gin.Context) error {
|
||||||
|
fmt.Println("🔍 BindAndValidate called")
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(data); err != nil {
|
||||||
|
fmt.Println("❌ Bind error:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✅ Bind success:", data)
|
||||||
|
|
||||||
|
if err := validate.Struct(data); err != nil {
|
||||||
|
fmt.Println("❌ Validation error:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✅ Validation success")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- "./backend:/app"
|
- "./backend:/app"
|
||||||
env_file:
|
env_file:
|
||||||
- ./backend/.env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
# - GIN_MODE=release # For production
|
# - GIN_MODE=release # For production
|
||||||
- GIN_MODE=debug
|
- GIN_MODE=debug
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ export default function SelectField({ data, label, className = "" }: SelectProps
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.handleChange}
|
onValueChange={(val) => {
|
||||||
onOpenChange={field.handleBlur}
|
field.handleChange(val);
|
||||||
|
field.handleBlur();
|
||||||
|
}}
|
||||||
defaultValue={field.state.value}
|
defaultValue={field.state.value}
|
||||||
>
|
>
|
||||||
<SelectTrigger className={"w-full"}>
|
<SelectTrigger className={"w-full"}>
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import renderQueryState from "@/components/RenderQueryState";
|
import renderQueryState from "@/components/RenderQueryState";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
import { useAppForm } from "@/hooks/formHook";
|
import { useAppForm } from "@/hooks/formHook";
|
||||||
import Authorised from "@/layouts/Authorised";
|
import Authorised from "@/layouts/Authorised";
|
||||||
import requests from "@/lib/requests";
|
import requests from "@/lib/requests";
|
||||||
import type { Template } from "@/types/api";
|
import type { Template } from "@/types/api";
|
||||||
import { useQuery, type UseQueryResult } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
export const Route = createFileRoute("/cover/create")({
|
export const Route = createFileRoute("/cover/create")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createSchema = z.object({
|
||||||
|
templateId: z.string().min(1, "Please select template"),
|
||||||
|
application: z.string().min(50, "Application is too short"),
|
||||||
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const templates = useQuery({
|
const templates = useQuery({
|
||||||
queryKey: ["user_templates"],
|
queryKey: ["user_templates"],
|
||||||
@@ -28,6 +33,12 @@ function RouteComponent() {
|
|||||||
templateId: "",
|
templateId: "",
|
||||||
application: "Paste job application here",
|
application: "Paste job application here",
|
||||||
},
|
},
|
||||||
|
validators: {
|
||||||
|
onBlur: createSchema,
|
||||||
|
},
|
||||||
|
onSubmit({ value }) {
|
||||||
|
console.log(JSON.stringify(value));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -41,7 +52,7 @@ function RouteComponent() {
|
|||||||
name="templateId"
|
name="templateId"
|
||||||
children={(f) => (
|
children={(f) => (
|
||||||
<f.SelectField
|
<f.SelectField
|
||||||
data={templates.data?.map((t) => ({ value: t.id, name: t.name }))}
|
data={templates.data?.map((t) => ({ value: `${t.id}`, name: t.name }))}
|
||||||
label={"Select template for cover letter"}
|
label={"Select template for cover letter"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -53,7 +64,9 @@ function RouteComponent() {
|
|||||||
<form.AppField name="application" children={(f) => <f.RichTextEdit />} />
|
<form.AppField name="application" children={(f) => <f.RichTextEdit />} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="mt-4">Generate cover letter</Button>
|
<Button onClick={form.handleSubmit} className="mt-4">
|
||||||
|
Generate cover letter
|
||||||
|
</Button>
|
||||||
</Authorised>
|
</Authorised>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user