Browse Source
feat(newsletter): add newsletter service, endpoints, and UI components
feat(newsletter): add newsletter service, endpoints, and UI components
Add Newsletter service to support functionality for unfollowing newsletters Add Newsletter REST controller and routing Implement newsletter-related endpoints and methods in the User Service Create UnfollowRequest for the INewsletterService interface Add MyListNewsletterResponse to user's data fields Add newsletter validation Refactor JS components to support newsletter type and simplify recipient forms by moving logic to FormRecipient component Refactor window global constants to support newsletters Modify server to initialize and use the newsletter services Add UI component for listing newsletters Refactor existing components to use FormRecipient for recipient data inputpull/204/head
27 changed files with 458 additions and 236 deletions
-
2src/cmd/root.go
-
11src/domains/newletter/newsletter.go
-
4src/domains/user/account.go
-
1src/domains/user/user.go
-
32src/internal/rest/newsletter.go
-
13src/internal/rest/user.go
-
22src/pkg/whatsapp/whatsapp.go
-
32src/services/newsletter.go
-
14src/services/user.go
-
20src/validations/newsletter_validation.go
-
25src/views/components/AccountAvatar.js
-
24src/views/components/AccountUserInfo.js
-
26src/views/components/MessageDelete.js
-
26src/views/components/MessageReact.js
-
26src/views/components/MessageRevoke.js
-
26src/views/components/MessageUpdate.js
-
115src/views/components/NewsletterList.js
-
25src/views/components/SendAudio.js
-
26src/views/components/SendContact.js
-
26src/views/components/SendFile.js
-
26src/views/components/SendImage.js
-
26src/views/components/SendLocation.js
-
25src/views/components/SendMessage.js
-
26src/views/components/SendPoll.js
-
26src/views/components/SendVideo.js
-
52src/views/components/generic/FormRecipient.js
-
17src/views/index.html
@ -0,0 +1,11 @@ |
|||||
|
package newletter |
||||
|
|
||||
|
import "context" |
||||
|
|
||||
|
type INewsletterService interface { |
||||
|
Unfollow(ctx context.Context, request UnfollowRequest) (err error) |
||||
|
} |
||||
|
|
||||
|
type UnfollowRequest struct { |
||||
|
NewsletterID string `json:"newsletter_id" form:"newsletter_id"` |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
package rest |
||||
|
|
||||
|
import ( |
||||
|
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newletter" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" |
||||
|
"github.com/gofiber/fiber/v2" |
||||
|
) |
||||
|
|
||||
|
type Newsletter struct { |
||||
|
Service domainNewsletter.INewsletterService |
||||
|
} |
||||
|
|
||||
|
func InitRestNewsletter(app *fiber.App, service domainNewsletter.INewsletterService) Newsletter { |
||||
|
rest := Newsletter{Service: service} |
||||
|
app.Post("/newsletter/unfollow", rest.Unfollow) |
||||
|
return rest |
||||
|
} |
||||
|
|
||||
|
func (controller *Newsletter) Unfollow(c *fiber.Ctx) error { |
||||
|
var request domainNewsletter.UnfollowRequest |
||||
|
err := c.BodyParser(&request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
err = controller.Service.Unfollow(c.UserContext(), request) |
||||
|
utils.PanicIfNeeded(err) |
||||
|
|
||||
|
return c.JSON(utils.ResponseData{ |
||||
|
Status: 200, |
||||
|
Code: "SUCCESS", |
||||
|
Message: "Success unfollow newsletter", |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
package services |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newletter" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" |
||||
|
"github.com/aldinokemal/go-whatsapp-web-multidevice/validations" |
||||
|
"go.mau.fi/whatsmeow" |
||||
|
) |
||||
|
|
||||
|
type newsletterService struct { |
||||
|
WaCli *whatsmeow.Client |
||||
|
} |
||||
|
|
||||
|
func NewNewsletterService(waCli *whatsmeow.Client) domainNewsletter.INewsletterService { |
||||
|
return &newsletterService{ |
||||
|
WaCli: waCli, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (service newsletterService) Unfollow(ctx context.Context, request domainNewsletter.UnfollowRequest) (err error) { |
||||
|
if err = validations.ValidateUnfollowNewsletter(ctx, request); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
JID, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.NewsletterID) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
return service.WaCli.UnfollowNewsletter(JID) |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
package validations |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newletter" |
||||
|
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" |
||||
|
validation "github.com/go-ozzo/ozzo-validation/v4" |
||||
|
) |
||||
|
|
||||
|
func ValidateUnfollowNewsletter(ctx context.Context, request domainNewsletter.UnfollowRequest) error { |
||||
|
err := validation.ValidateStructWithContext(ctx, &request, |
||||
|
validation.Field(&request.NewsletterID, validation.Required), |
||||
|
) |
||||
|
|
||||
|
if err != nil { |
||||
|
return pkgError.ValidationError(err.Error()) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
export default { |
||||
|
name: 'ListNewsletter', |
||||
|
data() { |
||||
|
return { |
||||
|
newsletters: [] |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
async openModal() { |
||||
|
try { |
||||
|
this.dtClear() |
||||
|
await this.submitApi(); |
||||
|
$('#modalNewsletterList').modal('show'); |
||||
|
this.dtRebuild() |
||||
|
showSuccessInfo("Newsletters fetched") |
||||
|
} catch (err) { |
||||
|
showErrorInfo(err) |
||||
|
} |
||||
|
}, |
||||
|
dtClear() { |
||||
|
$('#account_newsletters_table').DataTable().destroy(); |
||||
|
}, |
||||
|
dtRebuild() { |
||||
|
$('#account_newsletters_table').DataTable({ |
||||
|
"pageLength": 100, |
||||
|
"reloadData": true, |
||||
|
}).draw(); |
||||
|
}, |
||||
|
async handleUnfollowNewsletter(newsletter_id) { |
||||
|
try { |
||||
|
const ok = confirm("Are you sure to leave this newsletter?"); |
||||
|
if (!ok) return; |
||||
|
|
||||
|
await this.unfollowNewsletterApi(newsletter_id); |
||||
|
this.dtClear() |
||||
|
await this.submitApi(); |
||||
|
this.dtRebuild() |
||||
|
showSuccessInfo("Success unfollow newsletter") |
||||
|
} catch (err) { |
||||
|
showErrorInfo(err) |
||||
|
} |
||||
|
}, |
||||
|
async unfollowNewsletterApi(newsletter_id) { |
||||
|
try { |
||||
|
let payload = new FormData(); |
||||
|
payload.append("newsletter_id", newsletter_id) |
||||
|
await window.http.post(`/newsletter/unfollow`, payload) |
||||
|
} catch (error) { |
||||
|
if (error.response) { |
||||
|
throw new Error(error.response.data.message); |
||||
|
} |
||||
|
throw new Error(error.message); |
||||
|
|
||||
|
} |
||||
|
}, |
||||
|
async submitApi() { |
||||
|
try { |
||||
|
let response = await window.http.get(`/user/my/newsletters`) |
||||
|
this.newsletters = response.data.results.data; |
||||
|
} catch (error) { |
||||
|
if (error.response) { |
||||
|
throw new Error(error.response.data.message); |
||||
|
} |
||||
|
throw new Error(error.message); |
||||
|
} |
||||
|
}, |
||||
|
formatDate: function (value) { |
||||
|
if (!value) return '' |
||||
|
return moment.unix(value).format('LLL'); |
||||
|
} |
||||
|
}, |
||||
|
template: `
|
||||
|
<div class="green card" @click="openModal" style="cursor: pointer"> |
||||
|
<div class="content"> |
||||
|
<a class="ui green right ribbon label">Newsletter</a> |
||||
|
<div class="header">List Newsletters</div> |
||||
|
<div class="description"> |
||||
|
Display all your newsletters |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Modal AccountNewsletter --> |
||||
|
<div class="ui small modal" id="modalNewsletterList"> |
||||
|
<i class="close icon"></i> |
||||
|
<div class="header"> |
||||
|
My Newsletter List |
||||
|
</div> |
||||
|
<div class="content"> |
||||
|
<table class="ui celled table" id="account_newsletters_table"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>Newsletter ID</th> |
||||
|
<th>Name</th> |
||||
|
<th>Role</th> |
||||
|
<th>Created At</th> |
||||
|
<th>Action</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody v-if="newsletters != null"> |
||||
|
<tr v-for="n in newsletters"> |
||||
|
<td>{{ n.id.split('@')[0] }}</td> |
||||
|
<td>{{ n.thread_metadata.name.text }}</td> |
||||
|
<td>{{ n.viewer_metadata.role }}</td> |
||||
|
<td>{{ formatDate(n.thread_metadata.creation_time) }}</td> |
||||
|
<td> |
||||
|
<button class="ui red tiny button" @click="handleUnfollowNewsletter(n.id)">Unfollow</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
`
|
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
export default { |
||||
|
name: 'FormRecipient', |
||||
|
props: { |
||||
|
type: { |
||||
|
type: String, |
||||
|
required: true |
||||
|
}, |
||||
|
phone: { |
||||
|
type: String, |
||||
|
required: true |
||||
|
}, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
recipientTypes: [] |
||||
|
}; |
||||
|
}, |
||||
|
computed: { |
||||
|
phone_id() { |
||||
|
return this.phone + this.type; |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.recipientTypes = [ |
||||
|
{ value: window.TYPEUSER, text: 'Private Message' }, |
||||
|
{ value: window.TYPEGROUP, text: 'Group Message' }, |
||||
|
{ value: window.TYPENEWSLETTER, text: 'Newsletter' } |
||||
|
]; |
||||
|
}, |
||||
|
methods: { |
||||
|
updateType(event) { |
||||
|
this.$emit('update:type', event.target.value); |
||||
|
}, |
||||
|
updatePhone(event) { |
||||
|
this.$emit('update:phone', event.target.value); |
||||
|
} |
||||
|
}, |
||||
|
template: `
|
||||
|
<div class="field"> |
||||
|
<label>Type</label> |
||||
|
<select name="type" @change="updateType" class="ui dropdown"> |
||||
|
<option v-for="type in recipientTypes" :value="type.value">{{ type.text }}</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
|
||||
|
<div class="field"> |
||||
|
<label>Phone / Group ID</label> |
||||
|
<input :value="phone" aria-label="wa identifier" @input="updatePhone"> |
||||
|
<input :value="phone_id" disabled aria-label="whatsapp_id"> |
||||
|
</div> |
||||
|
`
|
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue