เพิ่ม ลบ แก้ไข Database ด้วย React
บทความนี้เกี่ยวข้องกับ ระบบ Admin ใช้เรียกดูข้อมูล แก้ไข ลบ ข้อมูล จาก ฐานข้อมูล (Database) ด้วย React
ข้อกำหนดเบื้องต้น
ข้อกำหนดสำหรับบทความนี้คือ คุณต้องปฏิบัติตามบทความ การสร้างฟอร์มใน React มาก่อน
การเพิ่มข้อมูล Back-End REST
ที่ Back-End ไฟล์ movie-handlers.go แก้ไขโค้ดดังนี้
package main
import (
"backend/models"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"time"
"github.com/julienschmidt/httprouter"
)
type jsonResp struct {
OK bool `json:"ok"`
Message string `json:"message"`
}
func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
id, err := strconv.Atoi(params.ByName("id"))
if err != nil {
app.logger.Print(errors.New("invalid id parameter"))
app.errorJSON(w, err)
return
}
movie, err := app.models.DB.Get(id)
err = app.writeJSON(w, http.StatusOK, movie, "movie")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMovies(w http.ResponseWriter, r *http.Request) {
movies, err := app.models.DB.All()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllGenres(w http.ResponseWriter, r *http.Request) {
genres, err := app.models.DB.GenresAll()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, genres, "genres")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMoviesByGenre(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
genreID, err := strconv.Atoi(params.ByName("genre_id"))
if err != nil {
app.errorJSON(w, err)
return
}
movies, err := app.models.DB.All(genreID)
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) deleteMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) insertMovie(w http.ResponseWriter, r *http.Request) {
}
type MoviePayload struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year string `json:"year"`
ReleaseDate string `json:"release_date"`
Runtime string `json:"runtime"`
Rating string `json:"rating"`
MPAARating string `json:"mpaa_rating"`
}
func (app *application) editmovie(w http.ResponseWriter, r *http.Request) {
var payload MoviePayload
err := json.NewDecoder(r.Body).Decode(&payload)
if err != nil {
log.Println(err)
app.errorJSON(w, err)
return
}
var movie models.Movie
movie.ID, _ = strconv.Atoi(payload.ID)
movie.Title = payload.Title
movie.Description = payload.Description
movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate)
movie.Year = movie.ReleaseDate.Year()
movie.Runtime, _ = strconv.Atoi(payload.Runtime)
movie.Rating, _ = strconv.Atoi(payload.Rating)
movie.MPAARating = payload.MPAARating
movie.CreatedAt = time.Now()
movie.UpdatedAt = time.Now()
err = app.models.DB.InsertMovie(movie)
if err != nil {
app.errorJSON(w, err)
return
}
ok := jsonResp{
OK: true,
}
err = app.writeJSON(w, http.StatusOK, ok, "response")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) searchMovies(w http.ResponseWriter, r *http.Request) {
}
ไฟล์ movies-db.go แก้ไขโค้ดดังนี้
package models
import (
"context"
"database/sql"
"fmt"
"log"
"time"
)
type DBModel struct {
DB *sql.DB
}
// Get returns one movie and error, if any
func (m *DBModel) Get(id int) (*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies where id = $1
`
row := m.DB.QueryRowContext(ctx, query, id)
var movie Movie
err := row.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
query = `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
rows, _ := m.DB.QueryContext(ctx, query, id)
defer rows.Close()
genres := make(map[int]string)
for rows.Next() {
var mg MovieGenre
err := rows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
movie.MovieGenre = genres
return &movie, nil
}
// All returns all movies and error, if any
func (m *DBModel) All(genre ...int) ([]*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
where := ""
if len(genre) > 0 {
where = fmt.Sprintf("where id in (select movie_id from movies_genres where genre_id = %d)", genre[0])
}
query := fmt.Sprintf(`select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies %s order by title`, where)
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var movies []*Movie
for rows.Next() {
var movie Movie
err := rows.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
genreQuery := `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
genreRows, _ := m.DB.QueryContext(ctx, genreQuery, movie.ID)
genres := make(map[int]string)
for genreRows.Next() {
var mg MovieGenre
err := genreRows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
genreRows.Close()
movie.MovieGenre = genres
movies = append(movies, &movie)
}
return movies, nil
}
func (m *DBModel) GenresAll() ([]*Genre, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, genre_name, created_at, updated_at from genres order by genre_name`
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var genres []*Genre
for rows.Next() {
var g Genre
err := rows.Scan(
&g.ID,
&g.GenreName,
&g.CreatedAt,
&g.UpdatedAt,
)
if err != nil {
return nil, err
}
genres = append(genres, &g)
}
return genres, nil
}
func (m *DBModel) InsertMovie(movie Movie) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
stmt := `insert into movies (title, description, year, release_date, runtime, rating, mpaa_rating,
created_at, updated_at) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
_, err := m.DB.ExecContext(ctx, stmt,
movie.Title,
movie.Description,
movie.Year,
movie.ReleaseDate,
movie.Runtime,
movie.Rating,
movie.MPAARating,
movie.CreatedAt,
movie.UpdatedAt,
)
if err != nil {
log.Println(err)
return err
}
return nil
}
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/0 ทดสอบคีย์ข้อมูล -> Save
ไปที่ เมนู Movies http://localhost:3000/movies จะพบรายการที่เพิ่มเข้ามา
เมื่อคลิกที่รายการ
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/0 ทดสอบคีย์ข้อมูล -> Save
ไปที่ เมนู Movies http://localhost:3000/movies จะพบรายการที่เพิ่มเข้ามา
pgAdmin 4 แสดงข้อมูลที่เพิ่มเข้าไป
Feedback พร้อมการแจ้งเตือนแบบใช้ซ้ำได้
Bootstrap Alerts ให้เป็นวิธีที่ง่ายต่อการสร้างข้อความแจ้งเตือน
ที่ Back-End ไฟล์ movie-handlers.go แก้ไขโค้ดดังนี้
package main
import (
"backend/models"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"time"
"github.com/julienschmidt/httprouter"
)
type jsonResp struct {
OK bool `json:"ok"`
Message string `json:"message"`
}
func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
id, err := strconv.Atoi(params.ByName("id"))
if err != nil {
app.logger.Print(errors.New("invalid id parameter"))
app.errorJSON(w, err)
return
}
movie, err := app.models.DB.Get(id)
err = app.writeJSON(w, http.StatusOK, movie, "movie")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMovies(w http.ResponseWriter, r *http.Request) {
movies, err := app.models.DB.All()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllGenres(w http.ResponseWriter, r *http.Request) {
genres, err := app.models.DB.GenresAll()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, genres, "genres")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMoviesByGenre(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
genreID, err := strconv.Atoi(params.ByName("genre_id"))
if err != nil {
app.errorJSON(w, err)
return
}
movies, err := app.models.DB.All(genreID)
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) deleteMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) insertMovie(w http.ResponseWriter, r *http.Request) {
}
type MoviePayload struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year string `json:"year"`
ReleaseDate string `json:"release_date"`
Runtime string `json:"runtime"`
Rating string `json:"rating"`
MPAARating string `json:"mpaa_rating"`
}
func (app *application) editmovie(w http.ResponseWriter, r *http.Request) {
var payload MoviePayload
err := json.NewDecoder(r.Body).Decode(&payload)
if err != nil {
log.Println(err)
app.errorJSON(w, err)
return
}
var movie models.Movie
movie.ID, _ = strconv.Atoi(payload.ID)
movie.Title = payload.Title
movie.Description = payload.Description
movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate)
movie.Year = movie.ReleaseDate.Year()
movie.Runtime, _ = strconv.Atoi(payload.Runtime)
movie.Rating, _ = strconv.Atoi(payload.Rating)
movie.MPAARating = payload.MPAARating
movie.CreatedAt = time.Now()
movie.UpdatedAt = time.Now()
if movie.ID == 0 {
err = app.models.DB.InsertMovie(movie)
if err != nil {
app.errorJSON(w, err)
return
}
}
ok := jsonResp{
OK: true,
}
err = app.writeJSON(w, http.StatusOK, ok, "response")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) searchMovies(w http.ResponseWriter, r *http.Request) {
}
Front-End ภายในโฟลเดอร์ components สร้างโฟลเดอร์ ui-components แล้วสร้างไฟล์ชื่อ Alert.js มีโค้ดดังนี้
const Alert = (props) => {
return(
<div className={`alert ${props.alertType}`} role="alert">
{props.alertMessage}
</div>
);
}
export default Alert;
ไฟล์ EditMovie.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
import Input from "./form-components/Input";
import TextArea from "./form-components/TextArea";
import Select from "./form-components/Select";
import Alert from "./ui-components/Alert";
export default class EditMovie extends Component {
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
mpaaOptions: [
{ id: "G", value: "G" },
{ id: "PG", value: "PG" },
{ id: "PG13", value: "PG13" },
{ id: "R", value: "R" },
{ id: "NC17", value: "NC17" },
],
isLoaded: false,
error: null,
errors: [],
alert: {
type: "d-none",
message: "",
},
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (evt) => {
evt.preventDefault();
// client side validation
let errors = [];
if (this.state.movie.title === "") {
errors.push("title");
}
this.setState({ errors: errors });
if (errors.length > 0) {
return false;
}
const data = new FormData(evt.target);
const payload = Object.fromEntries(data.entries());
console.log(payload);
const requestOptions = {
method: "POST",
body: JSON.stringify(payload),
};
fetch("http://localhost:4000/v1/admin/editmovie", requestOptions)
.then((response) => response.json())
.then((data) => {
if (data.error) {
this.setState({
alert: { type: "alert-danger", message: data.error.message },
});
} else {
this.setState({
alert: { type: "alert-success", message: "Changes saved!" },
});
}
});
};
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
},
}));
};
hasError(key) {
return this.state.errors.indexOf(key) !== -1;
}
componentDidMount() {
const id = this.props.match.params.id;
if (id > 0) {
fetch("http://localhost:4000/v1/movie/" + id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.Message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
const releaseDate = new Date(json.movie.release_date);
this.setState(
{
movie: {
id: id,
title: json.movie.title,
release_date: releaseDate.toISOString().split("T")[0],
runtime: json.movie.runtime,
mpaa_rating: json.movie.mpaa_rating,
rating: json.movie.rating,
description: json.movie.description,
},
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
} else {
this.setState({ isLoaded: true });
}
}
render() {
let { movie, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<Alert
alertType={this.state.alert.type}
alertMessage={this.state.alert.message}
/>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<Input
title={"Title"}
className={this.hasError("title") ? "is-invalid" : ""}
type={"text"}
name={"title"}
value={movie.title}
handleChange={this.handleChange}
errorDiv={this.hasError("title") ? "text-danger" : "d-none"}
errorMsg={"Please enter a title"}
/>
<Input
title={"Release Date"}
type={"date"}
name={"release_date"}
value={movie.release_date}
handleChange={this.handleChange}
/>
<Input
title={"Runtime"}
type={"text"}
name={"runtime"}
value={movie.runtime}
handleChange={this.handleChange}
/>
<Select
title={"MPAA Rating"}
name={"mpaa_rating"}
options={this.state.mpaaOptions}
value={movie.mpaa_rating}
handleChange={this.handleChange}
placeholder={"Choose..."}
/>
<Input
title={"Rating"}
type={"text"}
name={"rating"}
value={movie.rating}
handleChange={this.handleChange}
/>
<TextArea
title={"Description"}
name={"description"}
value={movie.description}
rows={"3"}
handleChange={this.handleChange}
/>
<hr />
<button className="btn btn-primary">Save</button>
</form>
</Fragment>
);
}
}
}
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/1 ทดสอบ -> Save
Edit Movie แก้ไขข้อมูล movie
ที่ Back-End ไฟล์ movies-db.go เขียนโค้ดดังนี้
package models
import (
"context"
"database/sql"
"fmt"
"log"
"time"
)
type DBModel struct {
DB *sql.DB
}
// Get returns one movie and error, if any
func (m *DBModel) Get(id int) (*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies where id = $1
`
row := m.DB.QueryRowContext(ctx, query, id)
var movie Movie
err := row.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
query = `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
rows, _ := m.DB.QueryContext(ctx, query, id)
defer rows.Close()
genres := make(map[int]string)
for rows.Next() {
var mg MovieGenre
err := rows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
movie.MovieGenre = genres
return &movie, nil
}
// All returns all movies and error, if any
func (m *DBModel) All(genre ...int) ([]*Movie, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
where := ""
if len(genre) > 0 {
where = fmt.Sprintf("where id in (select movie_id from movies_genres where genre_id = %d)", genre[0])
}
query := fmt.Sprintf(`select id, title, description, year, release_date, rating, runtime, mpaa_rating,
created_at, updated_at from movies %s order by title`, where)
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var movies []*Movie
for rows.Next() {
var movie Movie
err := rows.Scan(
&movie.ID,
&movie.Title,
&movie.Description,
&movie.Year,
&movie.ReleaseDate,
&movie.Rating,
&movie.Runtime,
&movie.MPAARating,
&movie.CreatedAt,
&movie.UpdatedAt,
)
if err != nil {
return nil, err
}
// get genres, if any
genreQuery := `select
mg.id, mg.movie_id, mg.genre_id, g.genre_name
from
movies_genres mg
left join genres g on (g.id = mg.genre_id)
where
mg.movie_id = $1
`
genreRows, _ := m.DB.QueryContext(ctx, genreQuery, movie.ID)
genres := make(map[int]string)
for genreRows.Next() {
var mg MovieGenre
err := genreRows.Scan(
&mg.ID,
&mg.MovieID,
&mg.GenreID,
&mg.Genre.GenreName,
)
if err != nil {
return nil, err
}
genres[mg.ID] = mg.Genre.GenreName
}
genreRows.Close()
movie.MovieGenre = genres
movies = append(movies, &movie)
}
return movies, nil
}
func (m *DBModel) GenresAll() ([]*Genre, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, genre_name, created_at, updated_at from genres order by genre_name`
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var genres []*Genre
for rows.Next() {
var g Genre
err := rows.Scan(
&g.ID,
&g.GenreName,
&g.CreatedAt,
&g.UpdatedAt,
)
if err != nil {
return nil, err
}
genres = append(genres, &g)
}
return genres, nil
}
func (m *DBModel) InsertMovie(movie Movie) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
stmt := `insert into movies (title, description, year, release_date, runtime, rating, mpaa_rating,
created_at, updated_at) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
_, err := m.DB.ExecContext(ctx, stmt,
movie.Title,
movie.Description,
movie.Year,
movie.ReleaseDate,
movie.Runtime,
movie.Rating,
movie.MPAARating,
movie.CreatedAt,
movie.UpdatedAt,
)
if err != nil {
log.Println(err)
return err
}
return nil
}
func (m *DBModel) UpdateMovie(movie Movie) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
stmt := `update movies set title = $1, description = $2, year = $3, release_date = $4,
runtime = $5, rating = $6, mpaa_rating = $7,
updated_at = $8 where id = $9`
_, err := m.DB.ExecContext(ctx, stmt,
movie.Title,
movie.Description,
movie.Year,
movie.ReleaseDate,
movie.Runtime,
movie.Rating,
movie.MPAARating,
movie.UpdatedAt,
movie.ID,
)
if err != nil {
log.Println(err)
return err
}
return nil
}
func (m *DBModel) DeleteMovie(id int) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
stmt := "delete from movies where id = $1"
_, err := m.DB.ExecContext(ctx, stmt, id)
if err != nil {
return err
}
return nil
}
ไฟล์ routes.go เขียนโค้ดดังนี้
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
func (app *application) routes() http.Handler {
router := httprouter.New()
router.HandlerFunc(http.MethodGet, "/status", app.statusHandler)
router.HandlerFunc(http.MethodGet, "/v1/movie/:id", app.getOneMovie)
router.HandlerFunc(http.MethodGet, "/v1/movies", app.getAllMovies)
router.HandlerFunc(http.MethodGet, "/v1/movies/:genre_id", app.getAllMoviesByGenre)
router.HandlerFunc(http.MethodGet, "/v1/genres", app.getAllGenres)
router.HandlerFunc(http.MethodPost, "/v1/admin/editmovie", app.editMovie)
router.HandlerFunc(http.MethodGet, "/v1/admin/deletemovie/:id", app.deleteMovie)
return app.enableCORS(router)
}
ไฟล์ movie-handlers.go เขียนโค้ดดังนี้
package main
import (
"backend/models"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"time"
"github.com/julienschmidt/httprouter"
)
type jsonResp struct {
OK bool `json:"ok"`
Message string `json:"message"`
}
func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
id, err := strconv.Atoi(params.ByName("id"))
if err != nil {
app.logger.Print(errors.New("invalid id parameter"))
app.errorJSON(w, err)
return
}
movie, err := app.models.DB.Get(id)
err = app.writeJSON(w, http.StatusOK, movie, "movie")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMovies(w http.ResponseWriter, r *http.Request) {
movies, err := app.models.DB.All()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllGenres(w http.ResponseWriter, r *http.Request) {
genres, err := app.models.DB.GenresAll()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, genres, "genres")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMoviesByGenre(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
genreID, err := strconv.Atoi(params.ByName("genre_id"))
if err != nil {
app.errorJSON(w, err)
return
}
movies, err := app.models.DB.All(genreID)
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) deleteMovie(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
id, err := strconv.Atoi(params.ByName("id"))
if err != nil {
app.errorJSON(w, err)
return
}
err = app.models.DB.DeleteMovie(id)
if err != nil {
app.errorJSON(w, err)
return
}
ok := jsonResp{
OK: true,
}
err = app.writeJSON(w, http.StatusOK, ok, "response")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) insertMovie(w http.ResponseWriter, r *http.Request) {
}
type MoviePayload struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year string `json:"year"`
ReleaseDate string `json:"release_date"`
Runtime string `json:"runtime"`
Rating string `json:"rating"`
MPAARating string `json:"mpaa_rating"`
}
func (app *application) editMovie(w http.ResponseWriter, r *http.Request) {
var payload MoviePayload
err := json.NewDecoder(r.Body).Decode(&payload)
if err != nil {
log.Println(err)
app.errorJSON(w, err)
return
}
var movie models.Movie
if payload.ID != "0" {
id, _ := strconv.Atoi(payload.ID)
m, _ := app.models.DB.Get(id)
movie = *m
movie.UpdatedAt = time.Now()
}
movie.ID, _ = strconv.Atoi(payload.ID)
movie.Title = payload.Title
movie.Description = payload.Description
movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate)
movie.Year = movie.ReleaseDate.Year()
movie.Runtime, _ = strconv.Atoi(payload.Runtime)
movie.Rating, _ = strconv.Atoi(payload.Rating)
movie.MPAARating = payload.MPAARating
movie.CreatedAt = time.Now()
movie.UpdatedAt = time.Now()
if movie.ID == 0 {
err = app.models.DB.InsertMovie(movie)
if err != nil {
app.errorJSON(w, err)
return
}
} else {
err = app.models.DB.UpdateMovie(movie)
if err != nil {
app.errorJSON(w, err)
return
}
}
ok := jsonResp{
OK: true,
}
err = app.writeJSON(w, http.StatusOK, ok, "response")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) searchMovies(w http.ResponseWriter, r *http.Request) {
}
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/1
ทดสอบเพิ่มจุด หลัง Two imprisoned men bond over a number of years -> Save
ไปที่ http://localhost:3000/movies/1 จะพบข้อมูลถูกแก้ไขแล้ว
Delete Movie
ที่ Front-End ติดตั้ง react-confirm-alert ด้วยคำสั่ง
npm install react-confirm-alert --save
ไฟล์ App.js เขียนโค้ดดังนี้
import React from 'react';
import {BrowserRouter as Router, Switch, Route, Link} from 'react-router-dom';
import Movies from './components/Movies';
import Admin from './components/Admin';
import Home from './components/Home';
import OneMovie from './components/OneMovie';
import Genres from './components/Genres';
import OneGenre from './components/OneGenre';
import EditMovie from './components/EditMovie';
export default function App() {
return (
<Router>
<div className="container">
<div className="row">
<h1 className="mt-3">
Go Watch a Movie!
</h1>
<hr className="mb-3"></hr>
</div>
<div className="row">
<div className="col-md-2">
<nav>
<ul className="list-group">
<li className="list-group-item">
<Link to="/">Home</Link>
</li>
<li className="list-group-item">
<Link to="/movies">Movies</Link>
</li>
<li className="list-group-item">
<Link to="/genres">Genres</Link>
</li>
<li className="list-group-item">
<Link to="/admin/movie/0">Add movie</Link>
</li>
<li className="list-group-item">
<Link to="/admin">Manage Catalogue</Link>
</li>
</ul>
</nav>
</div>
<div className="col-md-10">
<Switch>
<Route path="/movies/:id" component={OneMovie} />
<Route path="/movies">
<Movies />
</Route>
<Route path="/genre/:id" component={OneGenre} />
<Route exact path="/genres">
<Genres />
</Route>
<Route path="/admin/movie/:id" component={EditMovie} />
<Route path="/admin">
<Admin />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</div>
</div>
</Router>
);
}
ไฟล์ EditMovie.js เขียนโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
import "./EditMovie.css";
import Input from "./form-components/Input";
import Select from "./form-components/Select";
import TextArea from "./form-components/TextArea";
import Alert from "./ui-components/Alert";
import { confirmAlert } from "react-confirm-alert";
import "react-confirm-alert/src/react-confirm-alert.css";
export default class EditMovie extends Component {
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
mpaaOptions: [
{ id: "G", value: "G" },
{ id: "PG", value: "PG" },
{ id: "PG13", value: "PG13" },
{ id: "R", value: "R" },
{ id: "NC17", value: "NC17" },
],
isLoaded: false,
error: null,
errors: [],
alert: {
type: "d-none",
message: "",
},
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
const id = this.props.match.params.id;
if (id > 0) {
fetch("http://localhost:4000/v1/movie/" + id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
const releaseDate = new Date(json.movie.release_date);
this.setState(
{
movie: {
id: id,
title: json.movie.title,
release_date: releaseDate.toISOString().split("T")[0],
runtime: json.movie.runtime,
mpaa_rating: json.movie.mpaa_rating,
rating: json.movie.rating,
description: json.movie.description,
},
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
} else {
this.setState({ isLoaded: true });
}
}
handleSubmit = (evt) => {
evt.preventDefault();
// do validation
let errors = [];
if (this.state.movie.title === "") {
errors.push("title");
}
this.setState({ errors: errors });
if (errors.length > 0) {
return false;
}
// we passed, so post info
const data = new FormData(evt.target);
const payload = Object.fromEntries(data.entries());
const requestOptions = {
method: "POST",
// headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
};
fetch("http://localhost:4000/v1/admin/editmovie", requestOptions)
.then((response) => response.json())
.then((data) => {
if (data.error) {
alert(data.error.message);
this.setState({
alert: { type: "alert-danger", message: data.error.message },
});
} else {
this.props.history.push({
pathname: "/admin",
});
}
});
};
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
},
}));
};
// *** add this
hasError(key) {
return this.state.errors.indexOf(key) !== -1;
}
confirmDelete = (e) => {
console.log("would delete id", this.state.movie.id);
confirmAlert({
title: "Delete Movie?",
message: "Are you sure?",
buttons: [
{
label: "Yes",
onClick: () => {
// delete the movie
fetch(
"http://localhost:4000/v1/admin/deletemovie/" +
this.state.movie.id,
{ method: "GET" }
)
.then((response) => response.json)
.then((data) => {
if (data.error) {
this.setState({
alert: {
type: "alert-danger",
message: data.error.message,
},
});
} else {
this.setState({
alert: { type: "alert-success", message: "Movie deleted!" },
});
this.props.history.push({
pathname: "/admin",
});
}
});
},
},
{
label: "No",
onClick: () => {},
},
],
});
};
render() {
let { movie, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<Alert
alertType={this.state.alert.type}
alertMessage={this.state.alert.message}
/>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<Input
title={"Title"}
className={this.hasError("title") ? "is-invalid" : ""}
type={"text"}
name={"title"}
value={movie.title}
handleChange={this.handleChange}
errorDiv={this.hasError("title") ? "text-danger" : "d-none"}
errorMsg={"Please enter a title"}
/>
<Input
title={"Release Date"}
type={"date"}
name={"release_date"}
value={movie.release_date}
handleChange={this.handleChange}
/>
<Input
title={"Runtime"}
type={"text"}
name={"runtime"}
value={movie.runtime}
handleChange={this.handleChange}
/>
<Select
title={"MPAA Rating"}
name={"mpaa_rating"}
options={this.state.mpaaOptions}
value={movie.mpaa_rating}
handleChange={this.handleChange}
placeholder="Choose..."
/>
<Input
title={"Rating"}
type={"text"}
name={"rating"}
value={movie.rating}
handleChange={this.handleChange}
/>
<TextArea
title={"Description"}
name={"description"}
value={movie.description}
rows={"3"}
handleChange={this.handleChange}
/>
<hr />
<button className="btn btn-primary">Save</button>
<Link to="/admin" className="btn btn-warning ms-1">
Cancel
</Link>
{movie.id > 0 && (
<a
href="#!"
onClick={() => this.confirmDelete()}
className="btn btn-danger ms-1"
>
Delete
</a>
)}
</form>
</Fragment>
);
}
}
}
ไฟล์ Movies.js เขียนโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Movies extends Component {
state = {
movies: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies")
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
movies: json.movies,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
const { movies, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Choose a movie</h2>
<hr />
<div className="list-group">
{movies.map((m) => (
<Link
key={m.id}
className="list-group-item list-group-item-action"
to={`/movies/${m.id}`}
>
{m.title}
</Link>
))}
</div>
</Fragment>
);
}
}
}
ไฟล์ Admin.js เขียนโค้ดดังนี้
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
export default class Admin extends Component {
state = {
movies: [],
isLoaded: false,
error: null,
};
componentDidMount() {
fetch("http://localhost:4000/v1/movies")
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
this.setState(
{
movies: json.movies,
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
}
render() {
const { movies, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Manage Catalogue</h2>
<hr />
<div className="list-group">
{movies.map((m) => (
<Link
key={m.id}
className="list-group-item list-group-item-action"
to={`/admin/movie/${m.id}`}
>
{m.title}
</Link>
))}
</div>
</Fragment>
);
}
}
}
ลบข้อมูล movie
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/1 คลิกปุ่ม Delete
Yes
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/movies ข้อมูล ถูกลบไปแล้ว
credit : https://www.udemy.com/course/working-with-react-and-go-golang/