B: vendors working. How to make rules work
This commit is contained in:
parent
d36eb955ab
commit
5c7a72cf0f
|
@ -9,8 +9,7 @@ import (
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
Mongo *MongoConfig `mapstructure:"mongo"`
|
Mongo *MongoConfig `mapstructure:"mongo"`
|
||||||
YoutubeConfig *YoutubeConfig `mapstructure:"youtube"`
|
Vendors map[string]*VendorConfig `mapstructure:"vendors"`
|
||||||
PcoConfig *PcoConfig `mapstructure:"pco"`
|
|
||||||
JwtSecret string `mapstructure:"jwt_secret"`
|
JwtSecret string `mapstructure:"jwt_secret"`
|
||||||
Env string `mapstructure:"env"`
|
Env string `mapstructure:"env"`
|
||||||
}
|
}
|
||||||
|
@ -21,30 +20,21 @@ type MongoConfig struct {
|
||||||
EntCol string `mapstructure:"ent_col"`
|
EntCol string `mapstructure:"ent_col"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type YoutubeConfig struct {
|
type VendorConfig struct {
|
||||||
ClientId string `mapstructure:"client_id"`
|
ClientId string `mapstructure:"client_id"`
|
||||||
ClientSecret string `mapstructure:"client_secret"`
|
ClientSecret string `mapstructure:"client_secret"`
|
||||||
Scopes []string `mapstructure:"scopes"`
|
Scopes []string `mapstructure:"scopes"`
|
||||||
AuthUri string `mapstructure:"auth_uri"`
|
AuthUri string `mapstructure:"auth_uri"`
|
||||||
TokenUri string `mapstructure:"token_uri"`
|
TokenUri string `mapstructure:"token_uri"`
|
||||||
|
RefreshEncode string `mapstructure:"refresh_encode"`
|
||||||
scope string
|
scope string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (yt *YoutubeConfig) Scope() string {
|
func (pco *VendorConfig) Scope() string {
|
||||||
if yt.scope == "" {
|
if pco.scope == "" {
|
||||||
for i, str := range yt.Scopes {
|
pco.scope = strings.Join(pco.Scopes, " ")
|
||||||
yt.Scopes[i] = fmt.Sprintf("https://www.googleapis.com%s", str)
|
|
||||||
}
|
}
|
||||||
yt.scope = strings.Join(yt.Scopes, " ")
|
return pco.scope
|
||||||
}
|
|
||||||
return yt.scope
|
|
||||||
}
|
|
||||||
|
|
||||||
type PcoConfig struct {
|
|
||||||
ClientId string `mapstructure:"client_id"`
|
|
||||||
ClientSecret string `mapstructure:"client_secret"`
|
|
||||||
AuthUri string `mapstructure:"auth_uri"`
|
|
||||||
TokenUri string `mapstructure:"token_uri"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg *config
|
var cfg *config
|
||||||
|
@ -65,6 +55,11 @@ func Init() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%v\n", cfg)
|
||||||
|
for key, value := range cfg.Vendors {
|
||||||
|
fmt.Printf("%s: %v\n", key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Config() *config {
|
func Config() *config {
|
||||||
|
|
|
@ -50,4 +50,5 @@ func BuildRouter(r *gin.Engine) {
|
||||||
|
|
||||||
pco := vendor.Group("/pco")
|
pco := vendor.Group("/pco")
|
||||||
pco.POST("/initiate", InitiatePCOOuath)
|
pco.POST("/initiate", InitiatePCOOuath)
|
||||||
|
pco.GET("/callback", RecievePCOOuath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,128 @@
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config"
|
||||||
|
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PCO_REDIRECT_URI = "https://capstone.preston-baxter.com:8080/vendor/pco/callback"
|
||||||
|
|
||||||
func InitiatePCOOuath(c *gin.Context) {
|
func InitiatePCOOuath(c *gin.Context) {
|
||||||
c.String(200, "ok")
|
conf := config.Config()
|
||||||
|
vendorConfig := conf.Vendors[models.PCO_VENDOR_NAME]
|
||||||
|
|
||||||
|
init_url, err := url.Parse(vendorConfig.AuthUri)
|
||||||
|
if err != nil {
|
||||||
|
//we should not get here
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
q := init_url.Query()
|
||||||
|
q.Add("client_id", vendorConfig.ClientId)
|
||||||
|
q.Add("redirect_uri", PCO_REDIRECT_URI)
|
||||||
|
q.Add("response_type", "code")
|
||||||
|
q.Add("scope", vendorConfig.Scope())
|
||||||
|
init_url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
c.Redirect(302, init_url.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecievePCOOuath(c *gin.Context) {
|
||||||
|
conf := config.Config()
|
||||||
|
vendorConfig := conf.Vendors[models.PCO_VENDOR_NAME]
|
||||||
|
user := getUserFromContext(c)
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
log.Error("Unable to find user in context")
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Query("code")
|
||||||
|
//validate returned code
|
||||||
|
if code == "" {
|
||||||
|
log.Error("Youtube OAuth response did not contain a code. Possible CSRF")
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
|
||||||
|
token_url, err := url.Parse(vendorConfig.TokenUri)
|
||||||
|
if err != nil {
|
||||||
|
//we should not get here
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make request to google for credentials
|
||||||
|
q := token_url.Query()
|
||||||
|
|
||||||
|
q.Add("code", code)
|
||||||
|
q.Add("client_id", vendorConfig.ClientId)
|
||||||
|
q.Add("client_secret", vendorConfig.ClientSecret)
|
||||||
|
q.Add("redirect_uri", PCO_REDIRECT_URI)
|
||||||
|
q.Add("grant_type", "authorization_code")
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", token_url.String(), strings.NewReader(q.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("Failed to generate request with the following url: '%s'", token_url.String())
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("Failed to make request to the following url: '%s'", token_url.String())
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("Failed to read body from the following url: '%s'", token_url.String())
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
log.Errorf("Response failed with status code: %d. Error: %s", resp.StatusCode ,string(rawBody))
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthResp := &models.OauthCredential{}
|
||||||
|
err = json.Unmarshal(rawBody, oauthResp)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("Failed to Unmarshal response from the following url: '%s'", token_url.String())
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
}
|
||||||
|
log.Infof("oauthResp: %v", *oauthResp)
|
||||||
|
//Set expires at time but shave some time off to refresh token before expire date
|
||||||
|
oauthResp.ExpiresAt = time.Now().Add(time.Duration(oauthResp.ExpiresIn)*time.Second - 10)
|
||||||
|
|
||||||
|
//store credentials
|
||||||
|
vendor := &models.VendorAccount{
|
||||||
|
UserId: user.Id,
|
||||||
|
OauthCredentials: oauthResp,
|
||||||
|
Name: models.PCO_VENDOR_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mongo.SaveModel(vendor)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("Failed to save credentials for user: %s", user.Email)
|
||||||
|
c.AbortWithStatus(502)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(302, "/dashboard")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,13 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const REDIRECT_URI = "https://capstone.preston-baxter.com:8080/vendor/youtube/callback"
|
const YOUTUBE_REDIRECT_URI = "https://capstone.preston-baxter.com:8080/vendor/youtube/callback"
|
||||||
|
|
||||||
func InitiateYoutubeOuath(c *gin.Context) {
|
func InitiateYoutubeOuath(c *gin.Context) {
|
||||||
conf := config.Config()
|
conf := config.Config()
|
||||||
|
vendorConfig := conf.Vendors[models.YOUTUBE_VENDOR_NAME]
|
||||||
|
|
||||||
init_url, err := url.Parse(conf.YoutubeConfig.AuthUri)
|
init_url, err := url.Parse(vendorConfig.AuthUri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//we should not get here
|
//we should not get here
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -26,10 +27,10 @@ func InitiateYoutubeOuath(c *gin.Context) {
|
||||||
|
|
||||||
q := init_url.Query()
|
q := init_url.Query()
|
||||||
//https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#httprest_1
|
//https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#httprest_1
|
||||||
q.Add("client_id", conf.YoutubeConfig.ClientId)
|
q.Add("client_id", vendorConfig.ClientId)
|
||||||
q.Add("redirect_uri", REDIRECT_URI)
|
q.Add("redirect_uri", YOUTUBE_REDIRECT_URI)
|
||||||
q.Add("response_type", "code")
|
q.Add("response_type", "code")
|
||||||
q.Add("scope", conf.YoutubeConfig.Scope())
|
q.Add("scope", vendorConfig.Scope())
|
||||||
q.Add("access_type", "offline")
|
q.Add("access_type", "offline")
|
||||||
//used to prevent CSRF
|
//used to prevent CSRF
|
||||||
q.Add("state", getAuthHash(c))
|
q.Add("state", getAuthHash(c))
|
||||||
|
@ -40,6 +41,7 @@ func InitiateYoutubeOuath(c *gin.Context) {
|
||||||
|
|
||||||
func ReceiveYoutubeOauth(c *gin.Context) {
|
func ReceiveYoutubeOauth(c *gin.Context) {
|
||||||
conf := config.Config()
|
conf := config.Config()
|
||||||
|
vendorConfig := conf.Vendors[models.PCO_VENDOR_NAME]
|
||||||
user := getUserFromContext(c)
|
user := getUserFromContext(c)
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
|
@ -65,7 +67,7 @@ func ReceiveYoutubeOauth(c *gin.Context) {
|
||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
|
|
||||||
token_url, err := url.Parse(conf.YoutubeConfig.TokenUri)
|
token_url, err := url.Parse(vendorConfig.TokenUri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//we should not get here
|
//we should not get here
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -75,9 +77,9 @@ func ReceiveYoutubeOauth(c *gin.Context) {
|
||||||
q := token_url.Query()
|
q := token_url.Query()
|
||||||
|
|
||||||
q.Add("code", code)
|
q.Add("code", code)
|
||||||
q.Add("client_id", conf.YoutubeConfig.ClientId)
|
q.Add("client_id", vendorConfig.ClientId)
|
||||||
q.Add("client_secret", conf.YoutubeConfig.ClientSecret)
|
q.Add("client_secret", vendorConfig.ClientSecret)
|
||||||
q.Add("redirect_uri", REDIRECT_URI)
|
q.Add("redirect_uri", YOUTUBE_REDIRECT_URI)
|
||||||
q.Add("grant_type", "authorization_code")
|
q.Add("grant_type", "authorization_code")
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", token_url.String(), strings.NewReader(q.Encode()))
|
req, err := http.NewRequest("POST", token_url.String(), strings.NewReader(q.Encode()))
|
||||||
|
@ -124,7 +126,7 @@ func ReceiveYoutubeOauth(c *gin.Context) {
|
||||||
vendor := &models.VendorAccount{
|
vendor := &models.VendorAccount{
|
||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
OauthCredentials: oauthResp,
|
OauthCredentials: oauthResp,
|
||||||
Name: "youtube",
|
Name: models.YOUTUBE_VENDOR_NAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mongo.SaveModel(vendor)
|
err = mongo.SaveModel(vendor)
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OauthCredential struct {
|
||||||
|
AccessToken string `bson:"access_token,omitempty" json:"access_token,omitempty"`
|
||||||
|
ExpiresIn int `bson:"expires_in,omitempty" json:"expires_in,omitempty"`
|
||||||
|
ExpiresAt time.Time `bson:"expires_at,omitempty" json:"expires_at,omitempty"`
|
||||||
|
TokenType string `bson:"token_type,omitempty" json:"token_type,omitempty"`
|
||||||
|
Scope string `bson:"scope,omitempty" json:"scope,omitempty"`
|
||||||
|
RefreshToken string `bson:"refresh_token,omitempty" json:"refresh_token,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OauthRefreshBody struct {
|
||||||
|
ClientId string `json:"cleint_id"`
|
||||||
|
ClientSecret string `json:"cleint_secret"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oc *OauthCredential) RefreshAccessToken(vendor string) error {
|
||||||
|
conf := config.Config()
|
||||||
|
vendorConfig := conf.Vendors[vendor]
|
||||||
|
|
||||||
|
refresh_url, err := url.Parse(vendorConfig.TokenUri)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var body io.Reader
|
||||||
|
switch vendorConfig.RefreshEncode {
|
||||||
|
case "json":
|
||||||
|
refreshBody := OauthRefreshBody{
|
||||||
|
ClientId: vendorConfig.ClientId,
|
||||||
|
ClientSecret: vendorConfig.ClientSecret,
|
||||||
|
GrantType: "refresh_token",
|
||||||
|
RefreshToken: oc.RefreshToken,
|
||||||
|
}
|
||||||
|
raw, err := json.Marshal(&refreshBody)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
body = bytes.NewReader(raw)
|
||||||
|
case "url":
|
||||||
|
q := refresh_url.Query()
|
||||||
|
q.Add("client_id", vendorConfig.ClientId)
|
||||||
|
q.Add("client_secret", vendorConfig.ClientSecret)
|
||||||
|
q.Add("code", oc.RefreshToken)
|
||||||
|
q.Add("grant_type", "refresh_token")
|
||||||
|
|
||||||
|
body = strings.NewReader(q.Encode())
|
||||||
|
default:
|
||||||
|
panic(errors.New("Unkoown Encode Scheme"))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest("POST", refresh_url.String(), body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawBody, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
err = json.Unmarshal(rawBody, oc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oc.ExpiresAt = time.Now().Add(time.Duration(oc.ExpiresIn)*time.Second - 10)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VENDOR_ACCOUNT_TYPE = "vendor_account"
|
const VENDOR_ACCOUNT_TYPE = "vendor_account"
|
||||||
|
@ -13,14 +15,6 @@ const (
|
||||||
PCO_VENDOR_NAME = "PCO"
|
PCO_VENDOR_NAME = "PCO"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OauthCredential struct {
|
|
||||||
AccessToken string `bson:"access_token,omitempty" json:"access_token,omitempty"`
|
|
||||||
ExpiresIn int `bson:"expires_in,omitempty" json:"expires_in,omitempty"`
|
|
||||||
ExpiresAt time.Time `bson:"expires_at,omitempty" json:"expires_at,omitempty"`
|
|
||||||
TokenType string `bson:"token_type,omitempty" json:"token_type,omitempty"`
|
|
||||||
Scope string `bson:"scope,omitempty" json:"scope,omitempty"`
|
|
||||||
RefreshToken string `bson:"refresh_token,omitempty" json:"refresh_token,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VendorAccount struct {
|
type VendorAccount struct {
|
||||||
*CommonFields `bson:"obj_info"`
|
*CommonFields `bson:"obj_info"`
|
||||||
|
@ -49,3 +43,9 @@ func (va *VendorAccount) UpdateObjectInfo() {
|
||||||
}
|
}
|
||||||
va.UpdatedAt = now
|
va.UpdatedAt = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (va *VendorAccount) MakeRequest(req *http.Request, db *mongo.Client) error {
|
||||||
|
if va.OauthCredentials.ExpiresAt.Before(time.Now()) {
|
||||||
|
va.OauthCredentials.RefreshAccessToken(va.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config"
|
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config"
|
||||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
||||||
|
@ -31,3 +34,22 @@ func (db *DB) FindVendorAccountByUser(userId primitive.ObjectID) ([]models.Vendo
|
||||||
|
|
||||||
return vendors, nil
|
return vendors, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Make
|
||||||
|
func (db *DB) MakeRequestWithAccount(req *http.Request, va *models.VendorAccount) (*http.Response, error) {
|
||||||
|
//make new credential and save new credentials to DB
|
||||||
|
if va.OauthCredentials.ExpiresAt.Before(time.Now()) {
|
||||||
|
err := va.OauthCredentials.RefreshAccessToken(va.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = db.SaveModel(va)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("%s: %s", va.OauthCredentials.TokenType, va.OauthCredentials.AccessToken))
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
||||||
)
|
)
|
||||||
|
@ -227,9 +226,11 @@ templ DashboardVendorDropDown() {
|
||||||
Youtube
|
Youtube
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<a hx-post="/vendor/pco/initiate" href="" class="text-sm py-2 px-4 font-normal block w-full whitespace-nowrap bg-transparent text-blueGray-700">
|
<form action="/vendor/pco/initiate" method="POST">
|
||||||
PCO
|
<button type="submit" class="text-sm align-left py-2 px-4 font-normal block w-full whitespace-nowrap bg-transparent text-blueGray-700">
|
||||||
</a>
|
Planning Center
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -278,7 +279,7 @@ templ DashboardVendorWidget(vendors []models.VendorAccount) {
|
||||||
{ vendor.Name }
|
{ vendor.Name }
|
||||||
</th>
|
</th>
|
||||||
<th class="border-t-0 px-6 align-middle border-l-0 border-r-0 text-xs whitespace-nowrap p-4 text-left">
|
<th class="border-t-0 px-6 align-middle border-l-0 border-r-0 text-xs whitespace-nowrap p-4 text-left">
|
||||||
if vendor.OauthCredentials != nil && vendor.OauthCredentials.AccessToken != "" && vendor.OauthCredentials.ExpiresAt.Before(time.Now()) {
|
if vendor.OauthCredentials != nil && vendor.OauthCredentials.AccessToken != "" {
|
||||||
Active
|
Active
|
||||||
} else {
|
} else {
|
||||||
<button>Log in</button>
|
<button>Log in</button>
|
||||||
|
|
Loading…
Reference in New Issue