Merge pull request 'Add License' (#2) from license into main
Reviewed-on: https://git.altru.id/Furtuna/furtuna-backend/pulls/2
This commit is contained in:
commit
39c3f1a66d
78
internal/entity/license.go
Normal file
78
internal/entity/license.go
Normal file
@ -0,0 +1,78 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"`
|
||||
PartnerID int64 `gorm:"type:bigint;not null"`
|
||||
Name string `gorm:"type:varchar(255);not null"`
|
||||
StartDate time.Time `gorm:"type:date;not null"`
|
||||
EndDate time.Time `gorm:"type:date;not null"`
|
||||
RenewalDate *time.Time `gorm:"type:date"`
|
||||
SerialNumber string `gorm:"type:varchar(255);unique;not null"`
|
||||
CreatedBy int64 `gorm:"type:bigint;not null"`
|
||||
UpdatedBy int64 `gorm:"type:bigint;not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
func (License) TableName() string {
|
||||
return "licenses"
|
||||
}
|
||||
|
||||
type LicenseDB struct {
|
||||
License
|
||||
}
|
||||
|
||||
func (l *License) ToLicenseDB() *LicenseDB {
|
||||
return &LicenseDB{
|
||||
License: *l,
|
||||
}
|
||||
}
|
||||
|
||||
func (LicenseDB) TableName() string {
|
||||
return "licenses"
|
||||
}
|
||||
|
||||
func (e *LicenseDB) ToLicense() *License {
|
||||
return &License{
|
||||
ID: e.ID,
|
||||
PartnerID: e.PartnerID,
|
||||
Name: e.Name,
|
||||
StartDate: e.StartDate,
|
||||
EndDate: e.EndDate,
|
||||
RenewalDate: e.RenewalDate,
|
||||
SerialNumber: e.SerialNumber,
|
||||
CreatedBy: e.CreatedBy,
|
||||
UpdatedBy: e.UpdatedBy,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LicenseDB) ToUpdatedLicense(updatedBy int64, req License) {
|
||||
o.UpdatedBy = updatedBy
|
||||
|
||||
if req.Name != "" {
|
||||
o.Name = req.Name
|
||||
}
|
||||
|
||||
if !req.StartDate.IsZero() {
|
||||
o.StartDate = req.StartDate
|
||||
}
|
||||
|
||||
if !req.EndDate.IsZero() {
|
||||
o.EndDate = req.EndDate
|
||||
}
|
||||
|
||||
if req.RenewalDate != nil {
|
||||
o.RenewalDate = req.RenewalDate
|
||||
}
|
||||
|
||||
if req.SerialNumber != "" {
|
||||
o.SerialNumber = req.SerialNumber
|
||||
}
|
||||
}
|
||||
156
internal/handlers/http/license/license.go
Normal file
156
internal/handlers/http/license/license.go
Normal file
@ -0,0 +1,156 @@
|
||||
package license
|
||||
|
||||
import (
|
||||
"furtuna-be/internal/common/errors"
|
||||
"furtuna-be/internal/handlers/request"
|
||||
"furtuna-be/internal/handlers/response"
|
||||
"furtuna-be/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service services.License
|
||||
}
|
||||
|
||||
func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
|
||||
route := group.Group("/license")
|
||||
|
||||
route.POST("/", jwt, h.Create)
|
||||
route.GET("/", jwt, h.GetAll)
|
||||
route.PUT("/:id", jwt, h.Update)
|
||||
route.GET("/:id", jwt, h.GetByID)
|
||||
}
|
||||
|
||||
func NewHandler(service services.License) *Handler {
|
||||
return &Handler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// Create handles the creation of a new license.
|
||||
func (h *Handler) Create(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
|
||||
var req request.License
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWrapper(c, errors.ErrorBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
validate := validator.New()
|
||||
if err := validate.Struct(req); err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
licenseEntity, err := req.ToEntity()
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.service.Create(ctx, licenseEntity)
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var licenseResponse response.License
|
||||
licenseResponse.FromEntity(res)
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: licenseResponse,
|
||||
})
|
||||
}
|
||||
|
||||
// Update handles the update of an existing license.
|
||||
func (h *Handler) Update(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
|
||||
licenseID := c.Param("id")
|
||||
|
||||
var req request.License
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWrapper(c, errors.ErrorBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
validate := validator.New()
|
||||
if err := validate.Struct(req); err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
licenseEntity, err := req.ToEntity()
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
updatedLicense, err := h.service.Update(ctx, licenseID, licenseEntity)
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var licenseResponse response.License
|
||||
licenseResponse.FromEntity(updatedLicense)
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: licenseResponse,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAll retrieves a list of licenses.
|
||||
func (h *Handler) GetAll(c *gin.Context) {
|
||||
var req request.LicenseParam
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.ErrorWrapper(c, errors.ErrorBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
licenses, total, err := h.service.GetAll(c.Request.Context(), req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
licenseResponses := response.FromEntityList(licenses)
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: response.LicenseList{Licenses: licenseResponses, Total: total, Limit: req.Limit, Offset: req.Offset},
|
||||
})
|
||||
}
|
||||
|
||||
// GetByID retrieves details of a specific license by ID.
|
||||
func (h *Handler) GetByID(c *gin.Context) {
|
||||
licenseID := c.Param("id")
|
||||
|
||||
res, err := h.service.GetByID(c.Request.Context(), licenseID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, response.BaseResponse{
|
||||
Success: false,
|
||||
Status: http.StatusInternalServerError,
|
||||
Message: err.Error(),
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var licenseResponse response.License
|
||||
licenseResponse.FromEntity(res)
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: licenseResponse,
|
||||
})
|
||||
}
|
||||
51
internal/handlers/request/license.go
Normal file
51
internal/handlers/request/license.go
Normal file
@ -0,0 +1,51 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"furtuna-be/internal/entity"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
StartDate string `json:"start_date" validate:"required"`
|
||||
EndDate string `json:"end_date" validate:"required"`
|
||||
RenewalDate string `json:"renewal_date"`
|
||||
SerialNumber string `json:"serial_number" validate:"required"`
|
||||
PartnerID int64 `json:"partner_id" validate:"required"`
|
||||
}
|
||||
|
||||
type LicenseParam struct {
|
||||
Limit int `form:"limit,default=10"`
|
||||
Offset int `form:"offset,default=0"`
|
||||
}
|
||||
|
||||
func (r *License) ToEntity() (*entity.License, error) {
|
||||
startDate, err := time.Parse("2006-01-02", r.StartDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endDate, err := time.Parse("2006-01-02", r.EndDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var renewalDate *time.Time
|
||||
if r.RenewalDate != "" {
|
||||
parsedRenewalDate, err := time.Parse("2006-01-02", r.RenewalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
renewalDate = &parsedRenewalDate
|
||||
}
|
||||
|
||||
return &entity.License{
|
||||
ID: uuid.New(),
|
||||
Name: r.Name,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
RenewalDate: renewalDate,
|
||||
SerialNumber: r.SerialNumber,
|
||||
PartnerID: r.PartnerID,
|
||||
}, nil
|
||||
}
|
||||
46
internal/handlers/response/license.go
Normal file
46
internal/handlers/response/license.go
Normal file
@ -0,0 +1,46 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"furtuna-be/internal/entity"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
RenewalDate string `json:"renewal_date,omitempty"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
UpdatedBy int64 `json:"updated_by"`
|
||||
}
|
||||
|
||||
type LicenseList struct {
|
||||
Licenses []License `json:"licenses"`
|
||||
Total int64 `json:"total"`
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
}
|
||||
|
||||
func (r *License) FromEntity(e *entity.License) {
|
||||
r.ID = e.ID.String()
|
||||
r.Name = e.Name
|
||||
r.StartDate = e.StartDate.Format("2006-01-02")
|
||||
r.EndDate = e.EndDate.Format("2006-01-02")
|
||||
if e.RenewalDate != nil {
|
||||
r.RenewalDate = e.RenewalDate.Format("2006-01-02")
|
||||
}
|
||||
r.SerialNumber = e.SerialNumber
|
||||
r.PartnerID = e.PartnerID
|
||||
r.CreatedBy = e.CreatedBy
|
||||
r.UpdatedBy = e.UpdatedBy
|
||||
}
|
||||
|
||||
func FromEntityList(entities []*entity.License) []License {
|
||||
licenses := make([]License, len(entities))
|
||||
for i, e := range entities {
|
||||
licenses[i].FromEntity(e)
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
66
internal/repository/license/license.go
Normal file
66
internal/repository/license/license.go
Normal file
@ -0,0 +1,66 @@
|
||||
package license
|
||||
|
||||
import (
|
||||
"context"
|
||||
"furtuna-be/internal/common/logger"
|
||||
"furtuna-be/internal/entity"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LicenseRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewLicenseRepository(db *gorm.DB) *LicenseRepository {
|
||||
return &LicenseRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LicenseRepository) Create(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error) {
|
||||
if err := r.db.WithContext(ctx).Create(license).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating license", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return license, nil
|
||||
}
|
||||
|
||||
func (r *LicenseRepository) Update(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error) {
|
||||
if err := r.db.WithContext(ctx).Save(license).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating license", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return license, nil
|
||||
}
|
||||
|
||||
func (r *LicenseRepository) FindByID(ctx context.Context, id string) (*entity.LicenseDB, error) {
|
||||
licenseID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
license := new(entity.LicenseDB)
|
||||
if err := r.db.WithContext(ctx).First(license, licenseID).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when finding license by ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return license, nil
|
||||
}
|
||||
|
||||
func (r *LicenseRepository) GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error) {
|
||||
var licenses []*entity.License
|
||||
var total int64
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Find(&licenses).
|
||||
Count(&total).
|
||||
Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return licenses, total, nil
|
||||
}
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"furtuna-be/internal/repository/branches"
|
||||
"furtuna-be/internal/repository/brevo"
|
||||
"furtuna-be/internal/repository/license"
|
||||
mdtrns "furtuna-be/internal/repository/midtrans"
|
||||
"furtuna-be/internal/repository/orders"
|
||||
"furtuna-be/internal/repository/oss"
|
||||
@ -43,6 +44,7 @@ type RepoManagerImpl struct {
|
||||
Midtrans Midtrans
|
||||
Payment Payment
|
||||
EmailService EmailService
|
||||
License License
|
||||
}
|
||||
|
||||
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
||||
@ -63,6 +65,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
||||
Midtrans: mdtrns.New(&cfg.Midtrans),
|
||||
Payment: payment.NewPaymentRepository(db),
|
||||
EmailService: brevo.New(&cfg.Brevo),
|
||||
License: license.NewLicenseRepository(db),
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,3 +186,10 @@ type Payment interface {
|
||||
type EmailService interface {
|
||||
SendEmailTransactional(ctx context.Context, param entity.SendEmailNotificationParam) error
|
||||
}
|
||||
|
||||
type License interface {
|
||||
Create(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error)
|
||||
Update(ctx context.Context, license *entity.LicenseDB) (*entity.LicenseDB, error)
|
||||
FindByID(ctx context.Context, id string) (*entity.LicenseDB, error)
|
||||
GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package routes
|
||||
|
||||
import (
|
||||
"furtuna-be/internal/handlers/http/branch"
|
||||
"furtuna-be/internal/handlers/http/license"
|
||||
mdtrns "furtuna-be/internal/handlers/http/midtrans"
|
||||
"furtuna-be/internal/handlers/http/order"
|
||||
"furtuna-be/internal/handlers/http/oss"
|
||||
@ -56,6 +57,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
|
||||
partner.NewHandler(serviceManager.PartnerSvc),
|
||||
site.NewHandler(serviceManager.SiteSvc),
|
||||
mdtrns.NewHandler(serviceManager.OrderSvc),
|
||||
license.NewHandler(serviceManager.LicenseSvc),
|
||||
}
|
||||
|
||||
for _, handler := range serverRoutes {
|
||||
|
||||
71
internal/services/license/license.go
Normal file
71
internal/services/license/license.go
Normal file
@ -0,0 +1,71 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"furtuna-be/internal/common/logger"
|
||||
"furtuna-be/internal/common/mycontext"
|
||||
"furtuna-be/internal/entity"
|
||||
"furtuna-be/internal/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type LicenseService struct {
|
||||
repo repository.License
|
||||
}
|
||||
|
||||
func NewLicenseService(repo repository.License) *LicenseService {
|
||||
return &LicenseService{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LicenseService) Create(ctx mycontext.Context, licenseReq *entity.License) (*entity.License, error) {
|
||||
licenseDB := licenseReq.ToLicenseDB()
|
||||
licenseDB.CreatedBy = ctx.RequestedBy()
|
||||
licenseDB.UpdatedBy = ctx.RequestedBy()
|
||||
|
||||
licenseDB, err := s.repo.Create(ctx, licenseDB)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when create license", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return licenseDB.ToLicense(), nil
|
||||
}
|
||||
|
||||
func (s *LicenseService) Update(ctx mycontext.Context, id string, licenseReq *entity.License) (*entity.License, error) {
|
||||
existingLicense, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingLicense.ToUpdatedLicense(ctx.RequestedBy(), *licenseReq)
|
||||
|
||||
updatedLicenseDB, err := s.repo.Update(ctx, existingLicense)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when update license", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updatedLicenseDB.ToLicense(), nil
|
||||
}
|
||||
|
||||
func (s *LicenseService) GetByID(ctx context.Context, id string) (*entity.License, error) {
|
||||
licenseDB, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when get license by id", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return licenseDB.ToLicense(), nil
|
||||
}
|
||||
|
||||
func (s *LicenseService) GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error) {
|
||||
licenses, total, err := s.repo.GetAll(ctx, limit, offset)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when getting all licenses", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
return licenses, total, nil
|
||||
}
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"furtuna-be/internal/common/mycontext"
|
||||
"furtuna-be/internal/services/branch"
|
||||
service "furtuna-be/internal/services/license"
|
||||
"furtuna-be/internal/services/order"
|
||||
"furtuna-be/internal/services/oss"
|
||||
"furtuna-be/internal/services/partner"
|
||||
@ -32,6 +33,7 @@ type ServiceManagerImpl struct {
|
||||
OSSSvc OSSService
|
||||
PartnerSvc Partner
|
||||
SiteSvc Site
|
||||
LicenseSvc License
|
||||
}
|
||||
|
||||
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
|
||||
@ -46,7 +48,8 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
||||
OSSSvc: oss.NewOSSService(repo.OSS),
|
||||
PartnerSvc: partner.NewPartnerService(
|
||||
repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet),
|
||||
SiteSvc: site.NewSiteService(repo.Site),
|
||||
SiteSvc: site.NewSiteService(repo.Site),
|
||||
LicenseSvc: service.NewLicenseService(repo.License),
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,3 +126,10 @@ type Site interface {
|
||||
GetAll(ctx context.Context, search entity.SiteSearch) ([]*entity.Site, int, error)
|
||||
Delete(ctx mycontext.Context, id int64) error
|
||||
}
|
||||
|
||||
type License interface {
|
||||
Create(ctx mycontext.Context, licenseReq *entity.License) (*entity.License, error)
|
||||
Update(ctx mycontext.Context, id string, licenseReq *entity.License) (*entity.License, error)
|
||||
GetByID(ctx context.Context, id string) (*entity.License, error)
|
||||
GetAll(ctx context.Context, limit, offset int) ([]*entity.License, int64, error)
|
||||
}
|
||||
|
||||
1
migrations/000011_add-license-table.down.sql
Normal file
1
migrations/000011_add-license-table.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE licenses cascade;
|
||||
14
migrations/000011_add-license-table.up.sql
Normal file
14
migrations/000011_add-license-table.up.sql
Normal file
@ -0,0 +1,14 @@
|
||||
CREATE TABLE licenses
|
||||
(
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
partner_id NUMERIC NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
renewal_date DATE,
|
||||
serial_number VARCHAR(255) UNIQUE NOT NULL,
|
||||
created_by numeric not null,
|
||||
updated_by numeric not null,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
Loading…
x
Reference in New Issue
Block a user