working notely
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bootdotdev/learn-cicd-starter/internal/database"
|
"github.com/bootdotdev/learn-cicd-starter/internal/database"
|
||||||
"github.com/go-chi/chi"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,67 +19,6 @@ func (cfg *apiConfig) handlerNotesGet(w http.ResponseWriter, r *http.Request, us
|
|||||||
respondWithJSON(w, http.StatusOK, databasePostsToPosts(posts))
|
respondWithJSON(w, http.StatusOK, databasePostsToPosts(posts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *apiConfig) handlerNotesUpdate(w http.ResponseWriter, r *http.Request, user database.User) {
|
|
||||||
type parameters struct {
|
|
||||||
Note string `json:"note"`
|
|
||||||
}
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
|
||||||
params := parameters{}
|
|
||||||
err := decoder.Decode(¶ms)
|
|
||||||
if err != nil {
|
|
||||||
respondWithError(w, http.StatusInternalServerError, "Couldn't decode parameters")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
noteID := chi.URLParam(r, "noteID")
|
|
||||||
note, err := cfg.DB.GetNote(r.Context(), noteID)
|
|
||||||
if err != nil {
|
|
||||||
respondWithError(w, http.StatusNotFound, "Couldn't get note")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if note.UserID != user.ID {
|
|
||||||
respondWithError(w, http.StatusForbidden, "Can't update another user's note")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cfg.DB.UpdateNote(r.Context(), database.UpdateNoteParams{
|
|
||||||
UpdatedAt: time.Now().UTC(),
|
|
||||||
Note: params.Note,
|
|
||||||
ID: noteID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
respondWithError(w, http.StatusInternalServerError, "Couldn't update note")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
note, err = cfg.DB.GetNote(r.Context(), noteID)
|
|
||||||
if err != nil {
|
|
||||||
respondWithError(w, http.StatusNotFound, "Couldn't get note")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
respondWithJSON(w, http.StatusOK, databaseNoteToNote(note))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *apiConfig) handlerNotesDelete(w http.ResponseWriter, r *http.Request, user database.User) {
|
|
||||||
noteID := chi.URLParam(r, "noteID")
|
|
||||||
note, err := cfg.DB.GetNote(r.Context(), noteID)
|
|
||||||
if err != nil {
|
|
||||||
respondWithError(w, http.StatusNotFound, "Couldn't get note")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if note.UserID != user.ID {
|
|
||||||
respondWithError(w, http.StatusForbidden, "Can't update another user's note")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cfg.DB.DeleteNote(r.Context(), noteID)
|
|
||||||
if err != nil {
|
|
||||||
respondWithError(w, http.StatusInternalServerError, "Couldn't delete note")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
respondWithJSON(w, http.StatusOK, struct{}{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *apiConfig) handlerNotesCreate(w http.ResponseWriter, r *http.Request, user database.User) {
|
func (cfg *apiConfig) handlerNotesCreate(w http.ResponseWriter, r *http.Request, user database.User) {
|
||||||
type parameters struct {
|
type parameters struct {
|
||||||
Note string `json:"note"`
|
Note string `json:"note"`
|
||||||
|
|||||||
@@ -34,16 +34,6 @@ func (q *Queries) CreateNote(ctx context.Context, arg CreateNoteParams) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteNote = `-- name: DeleteNote :exec
|
|
||||||
|
|
||||||
DELETE FROM notes WHERE id = ?
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) DeleteNote(ctx context.Context, id string) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, deleteNote, id)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const getNote = `-- name: GetNote :one
|
const getNote = `-- name: GetNote :one
|
||||||
|
|
||||||
SELECT id, created_at, updated_at, note, user_id FROM notes WHERE id = ?
|
SELECT id, created_at, updated_at, note, user_id FROM notes WHERE id = ?
|
||||||
@@ -95,19 +85,3 @@ func (q *Queries) GetNotesForUser(ctx context.Context, userID string) ([]Note, e
|
|||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateNote = `-- name: UpdateNote :exec
|
|
||||||
|
|
||||||
UPDATE notes SET updated_at = ?, note = ? WHERE id = ?
|
|
||||||
`
|
|
||||||
|
|
||||||
type UpdateNoteParams struct {
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Note string
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) UpdateNote(ctx context.Context, arg UpdateNoteParams) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, updateNote, arg.UpdatedAt, arg.Note, arg.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -53,6 +53,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
dbQueries := database.New(db)
|
dbQueries := database.New(db)
|
||||||
apiCfg.DB = dbQueries
|
apiCfg.DB = dbQueries
|
||||||
|
log.Println("Connected to database!")
|
||||||
}
|
}
|
||||||
|
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
@@ -85,8 +86,6 @@ func main() {
|
|||||||
v1Router.Get("/users", apiCfg.middlewareAuth(apiCfg.handlerUsersGet))
|
v1Router.Get("/users", apiCfg.middlewareAuth(apiCfg.handlerUsersGet))
|
||||||
v1Router.Get("/notes", apiCfg.middlewareAuth(apiCfg.handlerNotesGet))
|
v1Router.Get("/notes", apiCfg.middlewareAuth(apiCfg.handlerNotesGet))
|
||||||
v1Router.Post("/notes", apiCfg.middlewareAuth(apiCfg.handlerNotesCreate))
|
v1Router.Post("/notes", apiCfg.middlewareAuth(apiCfg.handlerNotesCreate))
|
||||||
v1Router.Delete("/notes/{noteID}", apiCfg.middlewareAuth(apiCfg.handlerNotesDelete))
|
|
||||||
v1Router.Put("/notes/{noteID}", apiCfg.middlewareAuth(apiCfg.handlerNotesUpdate))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v1Router.Get("/healthz", handlerReadiness)
|
v1Router.Get("/healthz", handlerReadiness)
|
||||||
|
|||||||
@@ -10,11 +10,3 @@ SELECT * FROM notes WHERE id = ?;
|
|||||||
-- name: GetNotesForUser :many
|
-- name: GetNotesForUser :many
|
||||||
SELECT * FROM notes WHERE user_id = ?;
|
SELECT * FROM notes WHERE user_id = ?;
|
||||||
--
|
--
|
||||||
|
|
||||||
-- name: UpdateNote :exec
|
|
||||||
UPDATE notes SET updated_at = ?, note = ? WHERE id = ?;
|
|
||||||
--
|
|
||||||
|
|
||||||
-- name: DeleteNote :exec
|
|
||||||
DELETE FROM notes WHERE id = ?;
|
|
||||||
--
|
|
||||||
|
|||||||
@@ -6,38 +6,31 @@
|
|||||||
<title>Notely</title>
|
<title>Notely</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="section">
|
||||||
<h1>Notely</h1>
|
<h1>Notely</h1>
|
||||||
<button id="createUserButton">Create User</button>
|
|
||||||
|
|
||||||
<h2>Create Note</h2>
|
<div id="userCreationContainer" class="section">
|
||||||
<textarea id="newNoteContent"></textarea>
|
<input id="nameField" type="text" placeholder="Enter your name">
|
||||||
<button id="createNoteButton">Create Note</button>
|
<button id="createUserButton" onclick="createUser()">Create User</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Your Notes</h2>
|
<div id="noteSection" class="section" style="display: none;">
|
||||||
<div id="notes"></div>
|
<p id="greetingMessage"></p>
|
||||||
|
|
||||||
|
<textarea id="newNoteContent"></textarea>
|
||||||
|
<button id="createNoteButton" onclick="createNote()">Create Note</button>
|
||||||
|
|
||||||
|
<h2>Your Notes</h2>
|
||||||
|
<div id="notes"></div>
|
||||||
|
|
||||||
|
<button onclick="logout()">Logout</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const API_BASE = '/v1';
|
const API_BASE = '/v1';
|
||||||
let currentUserAPIKey = localStorage.getItem('currentUserAPIKey')
|
let currentUserAPIKey = null;
|
||||||
let currentUser = null;
|
let currentUser = null;
|
||||||
|
|
||||||
document.getElementById('createUserButton').addEventListener('click', createUser);
|
|
||||||
document.getElementById('createNoteButton').addEventListener('click', createNote);
|
|
||||||
|
|
||||||
async function createUser() {
|
|
||||||
const response = await fetch(`${API_BASE}/users`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ name: 'Test User' })
|
|
||||||
});
|
|
||||||
const user = await response.json();
|
|
||||||
localStorage.setItem('currentUserAPIKey', user.api_key);
|
|
||||||
currentUserAPIKey = user.api_key;
|
|
||||||
currentUser = user;
|
|
||||||
alert(`User Created: ${user.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createNote() {
|
async function createNote() {
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
alert('Please create a user first');
|
alert('Please create a user first');
|
||||||
@@ -51,7 +44,6 @@
|
|||||||
});
|
});
|
||||||
const note = await response.json();
|
const note = await response.json();
|
||||||
displayNote(note);
|
displayNote(note);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUser() {
|
async function getUser() {
|
||||||
@@ -59,11 +51,6 @@
|
|||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
|
||||||
localStorage.removeItem('currentUserAPIKey');
|
|
||||||
currentUser = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadNotes() {
|
async function loadNotes() {
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
return;
|
return;
|
||||||
@@ -82,14 +69,48 @@
|
|||||||
document.getElementById('notes').appendChild(noteElement);
|
document.getElementById('notes').appendChild(noteElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createUser() {
|
||||||
|
const nameField = document.getElementById('nameField');
|
||||||
|
const response = await fetch(`${API_BASE}/users`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ name: nameField.value }) // using the value from nameField
|
||||||
|
});
|
||||||
|
const user = await response.json();
|
||||||
|
localStorage.setItem('currentUserAPIKey', user.api_key);
|
||||||
|
login()
|
||||||
|
alert(`User Created: ${user.name}`);
|
||||||
|
|
||||||
|
// Once a user is created, hide the user creation section and show the note section
|
||||||
|
document.getElementById('userCreationContainer').style.display = 'none';
|
||||||
|
document.getElementById('noteSection').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
localStorage.removeItem('currentUserAPIKey');
|
||||||
|
currentUser = null;
|
||||||
|
|
||||||
|
// When a user logs out, show the user creation section and hide the note section
|
||||||
|
document.getElementById('userCreationContainer').style.display = 'block';
|
||||||
|
document.getElementById('noteSection').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
|
currentUserAPIKey = localStorage.getItem('currentUserAPIKey')
|
||||||
if (!currentUserAPIKey) {
|
if (!currentUserAPIKey) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const user = await getUser();
|
const user = await getUser();
|
||||||
currentUser = user;
|
currentUser = user;
|
||||||
currentUserAPIKey = user.api_key;
|
currentUserAPIKey = user.api_key;
|
||||||
await loadNotes();
|
await loadNotes();
|
||||||
|
|
||||||
|
// When a user logs in, hide the user creation section and show the note section
|
||||||
|
document.getElementById('userCreationContainer').style.display = 'none';
|
||||||
|
document.getElementById('noteSection').style.display = 'flex';
|
||||||
|
|
||||||
|
// Display a greeting message
|
||||||
|
document.getElementById('greetingMessage').textContent = `Hello ${user.name}!`;
|
||||||
}
|
}
|
||||||
|
|
||||||
login();
|
login();
|
||||||
@@ -110,21 +131,27 @@
|
|||||||
color: var(--light);
|
color: var(--light);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100vh;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
display: block;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
color: var(--light);
|
color: var(--light);
|
||||||
@@ -151,6 +178,7 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<!-- your existing CSS code... -->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user