Compare commits

..

9 Commits

Author SHA1 Message Date
Alf Sebastian Houge
0b715ef840 Fix warnings generated by go-staticcheck 2022-04-28 17:52:57 +02:00
Akhil Gupta
24105dbaaf Merge pull request #59 from meichthys/patch-1
remove whitespace from JWT_SECRET
2022-04-25 16:42:53 +05:30
Akhil Gupta
e3846634b5 Merge pull request #75 from AlfHou/icon-mobile
Add icon for mobile homescreens
2022-04-25 16:42:25 +05:30
Akhil Gupta
fd52c23636 Merge pull request #77 from AlfHou/mileage_on_odometer 2022-04-25 16:41:11 +05:30
Alf Sebastian Houge
20a1421576 Calculate mileage on odometer order instead of time 2022-03-15 14:26:46 +01:00
Alf Sebastian Houge
74e52c3e87 Change favicon to match mobile icon 2022-03-07 16:53:47 +01:00
Alf Sebastian Houge
1857bb0518 Change icon to gas pump icon instead of text 2022-03-07 10:41:28 +01:00
Alf Sebastian Houge
a729b5eb12 Add icon for mobile homescreens 2022-03-07 00:46:42 +01:00
MeIchthys
04f45fe385 remove whitespace from JWT_SECRET
When deploying with the whitespace around the `=`, docker complains.
2022-01-20 23:55:39 -05:00
13 changed files with 78 additions and 87 deletions

View File

@@ -4,7 +4,7 @@ services:
image: akhilrex/hammond
container_name: hammond
environment:
- JWT_SECRET = somethingverystrong
- JWT_SECRET=somethingverystrong
volumes:
- /path/to/config:/config
- /path/to/data:/assets

View File

@@ -94,17 +94,17 @@ func userLogin(c *gin.Context) {
user, err := db.FindOneUser(&db.User{Email: loginRequest.Email})
if err != nil {
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Not Registered email or invalid password")))
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("not Registered email or invalid password")))
return
}
if user.CheckPassword(loginRequest.Password) != nil {
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Not Registered email or invalid password")))
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("not Registered email or invalid password")))
return
}
if user.IsDisabled {
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Your user has been disabled by the admin. Please contact them to get it re-enabled.")))
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("your user has been disabled by the admin. Please contact them to get it re-enabled")))
return
}
UpdateContextUserModel(c, user.ID)
@@ -170,16 +170,16 @@ func changePassword(c *gin.Context) {
user, err := service.GetUserById(c.GetString("userId"))
if err != nil {
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("Not Registered email or invalid password")))
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("not Registered email or invalid password")))
return
}
if user.CheckPassword(request.OldPassword) != nil {
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("Incorrect old password")))
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("incorrect old password")))
return
}
user.SetPassword(request.NewPassword)
success, err := service.UpdatePassword(user.ID, request.NewPassword)
success, _ := service.UpdatePassword(user.ID, request.NewPassword)
c.JSON(http.StatusOK, success)
}

View File

@@ -23,8 +23,8 @@ func stripBearerPrefixFromTokenString(tok string) (string, error) {
// Extract token from Authorization header
// Uses PostExtractionFilter to strip "TOKEN " prefix from header
var AuthorizationHeaderExtractor = &request.PostExtractionFilter{
request.HeaderExtractor{"Authorization"},
stripBearerPrefixFromTokenString,
Extractor: request.HeaderExtractor{"Authorization"},
Filter: stripBearerPrefixFromTokenString,
}
// Extractor for OAuth2 access tokens. Looks in 'Authorization'

View File

@@ -51,7 +51,7 @@ func migrate(c *gin.Context) {
canMigrate, _, _ := db.CanMigrate(request.Url)
if !canMigrate {
c.JSON(http.StatusBadRequest, fmt.Errorf("cannot migrate database. please check connection string."))
c.JSON(http.StatusBadRequest, fmt.Errorf("cannot migrate database. please check connection string"))
return
}

View File

@@ -397,7 +397,7 @@ func deleteVehicle(c *gin.Context) {
return
}
if !canDelete {
c.JSON(http.StatusUnprocessableEntity, common.NewError("shareVehicle", errors.New("You are not allowed to delete this vehicle.")))
c.JSON(http.StatusUnprocessableEntity, common.NewError("shareVehicle", errors.New("you are not allowed to delete this vehicle")))
return
}
err = service.DeleteVehicle(searchByIdQuery.Id)

View File

@@ -117,7 +117,7 @@ func UnshareVehicle(vehicleId, userId string) error {
return nil
}
if mapping.IsOwner {
return fmt.Errorf("Cannot unshare owner")
return fmt.Errorf("cannot unshare owner")
}
result := DB.Where("id=?", mapping.ID).Delete(&UserVehicle{})
return result.Error
@@ -332,8 +332,7 @@ func UnlockMissedJobs() {
if (job.Date == time.Time{}) {
continue
}
var duration time.Duration
duration = time.Duration(job.Duration)
var duration = time.Duration(job.Duration)
d := job.Date.Add(time.Minute * duration)
if d.Before(time.Now()) {
fmt.Println(job.Name + " is unlocked")

View File

@@ -3,7 +3,6 @@ package service
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"net/http"
@@ -126,14 +125,14 @@ func CreateBackup() (string, error) {
tarballFilePath := path.Join(folder, backupFileName)
file, err := os.Create(tarballFilePath)
if err != nil {
return "", errors.New(fmt.Sprintf("Could not create tarball file '%s', got error '%s'", tarballFilePath, err.Error()))
return "", fmt.Errorf("could not create tarball file '%s', got error '%s'", tarballFilePath, err.Error())
}
defer file.Close()
dbPath := path.Join(configPath, "hammond.db")
_, err = os.Stat(dbPath)
if err != nil {
return "", errors.New(fmt.Sprintf("Could not find db file '%s', got error '%s'", dbPath, err.Error()))
return "", fmt.Errorf("could not find db file '%s', got error '%s'", dbPath, err.Error())
}
gzipWriter := gzip.NewWriter(file)
defer gzipWriter.Close()
@@ -151,13 +150,13 @@ func CreateBackup() (string, error) {
func addFileToTarWriter(filePath string, tarWriter *tar.Writer) error {
file, err := os.Open(filePath)
if err != nil {
return errors.New(fmt.Sprintf("Could not open file '%s', got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not open file '%s', got error '%s'", filePath, err.Error())
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return errors.New(fmt.Sprintf("Could not get stat for file '%s', got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not get stat for file '%s', got error '%s'", filePath, err.Error())
}
header := &tar.Header{
@@ -169,12 +168,12 @@ func addFileToTarWriter(filePath string, tarWriter *tar.Writer) error {
err = tarWriter.WriteHeader(header)
if err != nil {
return errors.New(fmt.Sprintf("Could not write header for file '%s', got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not write header for file '%s', got error '%s'", filePath, err.Error())
}
_, err = io.Copy(tarWriter, file)
if err != nil {
return errors.New(fmt.Sprintf("Could not copy the file '%s' data to the tarball, got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not copy the file '%s' data to the tarball, got error '%s'", filePath, err.Error())
}
return nil

View File

@@ -1,6 +1,7 @@
package service
import (
"sort"
"time"
"github.com/akhilrex/hammond/db"
@@ -15,6 +16,9 @@ func GetMileageByVehicleId(vehicleId string, since time.Time) (mileage []models.
fillups := make([]db.Fillup, len(*data))
copy(fillups, *data)
sort.Slice(fillups, func(i, j int) bool {
return fillups[i].OdoReading > fillups[j].OdoReading
})
var mileages []models.MileageModel

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 895 B

View File

@@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="shortcut icon" href="<%= webpackConfig.output.publicPath %>hammond.png" />
<link rel="apple-touch-icon" href="<%= webpackConfig.output.publicPath %>touch-icon.png" />
<title><%= webpackConfig.name %></title>
</head>
<body>

BIN
ui/public/touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -21,27 +21,13 @@ export default {
email: '',
password: '',
distanceUnit: 1,
currency: '',
currency: 'INR',
},
}
},
computed: {
...mapGetters('auth', ['isInitialized']),
...mapState('vehicles', ['currencyMasters', 'distanceUnitMasters']),
filteredCurrencyMasters() {
return this.currencyMasters.filter((option) => {
return (
option.namePlural
.toString()
.toLowerCase()
.indexOf(this.registerModel.currency.toLowerCase()) >= 0 ||
option.code
.toString()
.toLowerCase()
.indexOf(this.registerModel.currency.toLowerCase()) >= 0
)
})
},
},
mounted() {
store.dispatch('vehicles/fetchMasters').then((data) => {})
@@ -153,9 +139,6 @@ export default {
})
.finally(() => (this.isWorking = false))
},
formatCurrency(option) {
return `${option.namePlural} (${option.code})`
},
},
}
</script>
@@ -165,10 +148,15 @@ export default {
<div v-if="!migrationMode" class="box">
<h1 class="title">Migrate from Clarkson</h1>
<p>
If you have an existing Clarkson deployment and you want to migrate your data from that, press the following button.
If you have an existing Clarkson deployment and you want to migrate your data from that,
press the following button.
</p>
<br />
<b-field> <b-button type="is-primary" @click="migrationMode = 'clarkson'">Migrate from Clarkson</b-button></b-field>
<b-field>
<b-button type="is-primary" @click="migrationMode = 'clarkson'"
>Migrate from Clarkson</b-button
></b-field
>
</div>
<div v-if="!migrationMode" class="box">
<h1 class="title">Fresh Install</h1>
@@ -182,12 +170,21 @@ export default {
</div>
<div v-if="migrationMode === 'clarkson'" class="box content">
<h1 class="title">Migrate from Clarkson</h1>
<p>You need to make sure that this deployment of Hammond can access the MySQL database used by Clarkson.</p>
<p>If that is not directly possible, you can make a copy of that database somewhere accessible from this instance.</p>
<p>Once that is done, enter the connection string to the MySQL instance in the following format.</p>
<p
>All the users imported from Clarkson will have their username as their email in Clarkson database and pasword set to
<span class="" style="font-weight:bold">hammond</span></p
>You need to make sure that this deployment of Hammond can access the MySQL database used by
Clarkson.</p
>
<p
>If that is not directly possible, you can make a copy of that database somewhere accessible
from this instance.</p
>
<p
>Once that is done, enter the connection string to the MySQL instance in the following
format.</p
>
<p
>All the users imported from Clarkson will have their username as their email in Clarkson
database and pasword set to <span class="" style="font-weight:bold">hammond</span></p
>
<code>
user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
@@ -203,8 +200,15 @@ export default {
</b-field>
<div class="buttons">
<b-button v-if="!testSuccess" type="is-primary" :disabled="isWorking" @click="testConnection">Test Connection</b-button
><b-button v-if="testSuccess" type="is-success" :disabled="isWorking" @click="migrate">Migrate</b-button>
<b-button
v-if="!testSuccess"
type="is-primary"
:disabled="isWorking"
@click="testConnection"
>Test Connection</b-button
><b-button v-if="testSuccess" type="is-success" :disabled="isWorking" @click="migrate"
>Migrate</b-button
>
<b-button type="is-danger is-light" @click="resetMigrationMode">Cancel</b-button>
</div>
</div>
@@ -218,22 +222,28 @@ export default {
<b-input v-model="registerModel.email" type="email" required></b-input>
</b-field>
<b-field label="Your Password">
<b-input v-model="registerModel.password" type="password" required minlength="8" password-reveal></b-input>
<b-input
v-model="registerModel.password"
type="password"
required
minlength="8"
password-reveal
></b-input>
</b-field>
<b-field label="Currency">
<b-autocomplete
v-model="registerModel.currency"
:custom-formatter="formatCurrency"
placeholder="Currency"
:data="filteredCurrencyMasters"
:keep-first="true"
:open-on-focus="true"
required
@select="(option) => (selected = option)"
></b-autocomplete>
<b-select v-model="registerModel.currency" placeholder="Currency" required expanded>
<option v-for="option in currencyMasters" :key="option.code" :value="option.code">
{{ `${option.namePlural} (${option.code})` }}
</option>
</b-select>
</b-field>
<b-field label="Distance Unit">
<b-select v-model.number="registerModel.distanceUnit" placeholder="Distance Unit" required expanded>
<b-select
v-model.number="registerModel.distanceUnit"
placeholder="Distance Unit"
required
expanded
>
<option v-for="(option, key) in distanceUnitMasters" :key="key" :value="key">
{{ `${option.long} (${option.short})` }}
</option>

View File

@@ -44,20 +44,6 @@ export default {
return this.changePassModel.new === this.changePassModel.renew
},
filteredCurrencyMasters() {
return this.currencyMasters.filter((option) => {
return (
option.namePlural
.toString()
.toLowerCase()
.indexOf(this.settingsModel.currency.toLowerCase()) >= 0 ||
option.code
.toString()
.toLowerCase()
.indexOf(this.settingsModel.currency.toLowerCase()) >= 0
)
})
},
},
methods: {
changePassword() {
@@ -123,9 +109,6 @@ export default {
this.tryingToSave = false
})
},
formatCurrency(option) {
return `${option.namePlural} (${option.code})`
},
},
}
</script>
@@ -140,16 +123,11 @@ export default {
These will be used as default values whenever you create a new fillup or expense.
</h1>
<b-field label="Currency">
<b-autocomplete
v-model="settingsModel.currency"
:custom-formatter="formatCurrency"
placeholder="Currency"
:data="filteredCurrencyMasters"
:keep-first="true"
:open-on-focus="true"
required
@select="(option) => (selected = option)"
></b-autocomplete>
<b-select v-model="settingsModel.currency" placeholder="Currency" required expanded>
<option v-for="option in currencyMasters" :key="option.code" :value="option.code">
{{ `${option.namePlural} (${option.code})` }}
</option>
</b-select>
</b-field>
<b-field label="Distance Unit">
<b-select v-model.number="settingsModel.distanceUnit" placeholder="Distance Unit" required expanded>