...
 
......@@ -143,6 +143,60 @@ func (budget *Budget) Balance(context *APIContext) (int64, error) {
return val, err
}
// BalanceStats returns this budget's total balance for the past months
func (budget *Budget) BalanceStats(context *APIContext) ([]int64, error) {
var val []int64
lom := time.Now().UTC()
b := int64(-1)
for b != 0 {
err := context.QueryRow("SELECT COALESCE(SUM(amount), 0) FROM transactions WHERE budget_id = $1 AND created_at <= $2", budget.ID, lom).
Scan(&b)
if err != nil {
return val, err
}
val = append(val, b)
fom := time.Date(lom.Year(), lom.Month(), 1, 0, 0, 0, 0, time.UTC)
lom = fom.AddDate(0, 0, 0).Add(time.Nanosecond * -1)
}
return val, nil
}
// SearchBudgets searches database for budgets
func (context *APIContext) SearchBudgets(term string) ([]Budget, error) {
budgets := []Budget{}
rows, err := context.Query("SELECT DISTINCT budgets.id FROM budgets, projects "+
"WHERE projects.id = budgets.project_id AND "+
"(LOWER(budgets.name) LIKE LOWER('%' || $1 || '%') OR "+
"LOWER(projects.name) LIKE LOWER('%' || $1 || '%') OR "+
"LOWER(budgets.description) LIKE LOWER('%' || $1 || '%'))", term)
if err != nil {
return budgets, err
}
defer rows.Close()
for rows.Next() {
var id int64
err = rows.Scan(&id)
if err != nil {
return budgets, err
}
p, err := context.LoadBudgetByID(id)
if err != nil {
return budgets, err
}
budgets = append(budgets, p)
}
return budgets, nil
}
// BudgetRatioPair represents a pair of budgets and ratios
type BudgetRatioPair struct {
budgetIDs []string
......
......@@ -177,3 +177,31 @@ func (code *Code) Save(context *APIContext) error {
return err
}
*/
// SearchCodes searches database for codes
func (context *APIContext) SearchCodes(term string) ([]Code, error) {
codes := []Code{}
rows, err := context.Query("SELECT DISTINCT codes.id, codes.code, codes.budget_ids, codes.ratios, codes.user_id FROM codes, projects, "+
"UNNEST(codes.budget_ids) bid LEFT JOIN budgets ON budgets.id=bid "+
"WHERE projects.id = budgets.project_id AND "+
"(LOWER(codes.code) LIKE LOWER('%' || $1 || '%') OR "+
"LOWER(budgets.name) LIKE LOWER('%' || $1 || '%') OR "+
"LOWER(projects.name) LIKE LOWER('%' || $1 || '%'))", term)
if err != nil {
return codes, err
}
defer rows.Close()
for rows.Next() {
code := Code{}
err = rows.Scan(&code.ID, &code.Code, &code.BudgetIDs, &code.Ratios, &code.UserID)
if err != nil {
return codes, err
}
codes = append(codes, code)
}
return codes, err
}
......@@ -140,3 +140,79 @@ func (project *Project) Contributors(context *APIContext) ([]User, error) {
return users, err
}
// Balance returns this project's total balance
func (project *Project) Balance(context *APIContext) (int64, error) {
var b int64
budgets, err := context.LoadBudgets(project)
if err != nil {
return 0, err
}
for _, budget := range budgets {
bal, err := budget.Balance(context)
if err != nil {
return 0, err
}
b += bal
}
return b, nil
}
// BalanceStats returns this project's total balances for the past months
func (project *Project) BalanceStats(context *APIContext) ([]int64, error) {
var b []int64
budgets, err := context.LoadBudgets(project)
if err != nil {
return b, err
}
for _, budget := range budgets {
bal, err := budget.BalanceStats(context)
if err != nil {
return b, err
}
for idx, v := range bal {
if idx >= len(b) {
b = append(b, 0)
}
b[idx] += v
}
}
return b, nil
}
// SearchProjects searches database for projects
func (context *APIContext) SearchProjects(term string) ([]Project, error) {
projects := []Project{}
rows, err := context.Query("SELECT DISTINCT id FROM projects "+
"WHERE (LOWER(name) LIKE LOWER('%' || $1 || '%') OR "+
"LOWER(summary) LIKE LOWER('%' || $1 || '%'))", term)
if err != nil {
return projects, err
}
defer rows.Close()
for rows.Next() {
var id int64
err = rows.Scan(&id)
if err != nil {
return projects, err
}
p, err := context.GetProjectByID(id)
if err != nil {
return projects, err
}
projects = append(projects, p)
}
return projects, nil
}
package db
// Search represents the db schema of a search
type Search struct {
ID string
Projects []Project
Budgets []Budget
Payments []Payment
}
// Search searches the database for projects, budgets & payments
func (context *APIContext) Search(term string) (Search, error) {
search := Search{
ID: term,
}
var err error
search.Projects, err = context.SearchProjects(term)
if err != nil {
return search, err
}
search.Budgets, err = context.SearchBudgets(term)
if err != nil {
return search, err
}
search.Payments, err = context.SearchPayments(term)
if err != nil {
return search, err
}
return search, nil
}
package db
import (
"math"
"strconv"
)
// Statistics represents the db schema of a statistic
type Statistics struct {
ID string
ProjectID int64
BudgetID int64
MonthlyChange int64
PastMonths []int64
}
// LoadStatistics loads statistics for a project from the database
func (context *APIContext) LoadStatistics(projectID int64) (Statistics, error) {
p, err := context.GetProjectByID(projectID)
if err != nil {
return Statistics{}, err
}
bal, err := p.Balance(context)
if err != nil {
return Statistics{}, err
}
stats := Statistics{
ID: "stats_" + strconv.FormatInt(projectID, 10),
ProjectID: projectID,
}
bs, err := p.BalanceStats(context)
if err == nil {
for _, v := range bs {
stats.PastMonths = append(stats.PastMonths, v)
}
}
max := math.Min(float64(len(stats.PastMonths))-1, 11)
start := stats.PastMonths[int(max)]
stats.MonthlyChange = int64(float64(bal-start) / (max + 1))
return stats, nil
}
......@@ -28,9 +28,8 @@ func (r *BudgetResource) DeleteParams() []*restful.Parameter {
func (r *BudgetResource) Delete(context smolder.APIContext, request *restful.Request, response *restful.Response) {
auth, err := context.Authentication(request)
if err != nil || auth.(db.Budget).ID != 1 {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusUnauthorized,
false,
"Admin permission required for this operation",
"BudgetResource DELETE"))
return
......@@ -45,9 +44,8 @@ func (r *BudgetResource) Delete(context smolder.APIContext, request *restful.Req
err = budget.Delete(ctx)
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't delete budget",
"BudgetResource DELETE"))
return
......
......@@ -52,10 +52,8 @@ func (r *BudgetResource) Post(context smolder.APIContext, data interface{}, requ
project, err := context.(*db.APIContext).LoadProjectByUUID(ups.Budget.Project)
if err != nil {
panic(err)
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusBadRequest,
"No such project",
"BudgetResource POST"))
return
......@@ -71,10 +69,8 @@ func (r *BudgetResource) Post(context smolder.APIContext, data interface{}, requ
}
err = budget.Save(context.(*db.APIContext))
if err != nil {
panic(err)
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't create budget",
"BudgetResource POST"))
return
......
......@@ -53,10 +53,8 @@ func (r *BudgetResource) Put(context smolder.APIContext, data interface{}, reque
pps := data.(*BudgetPutStruct)
project, err := context.(*db.APIContext).LoadProjectByUUID(pps.Budget.Project)
if err != nil {
panic(err)
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusBadRequest,
"No such project",
"BudgetResource POST"))
return
......@@ -69,9 +67,8 @@ func (r *BudgetResource) Put(context smolder.APIContext, data interface{}, reque
err = budget.Update(context.(*db.APIContext))
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't update budget",
"BudgetResource PUT"))
return
......
......@@ -77,9 +77,8 @@ func (r *PaymentResource) Post(context smolder.APIContext, data interface{}, req
fmt.Println(string(body))
if resp.StatusCode != http.StatusOK {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, nil, smolder.NewErrorResponse(
http.StatusBadRequest,
false,
"Unknown payment ID",
"PaymentResource POST"))
return
......@@ -87,9 +86,8 @@ func (r *PaymentResource) Post(context smolder.APIContext, data interface{}, req
err = json.Unmarshal(body, &payments)
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Error decoding payment response",
"PaymentResource POST"))
return
......@@ -116,9 +114,8 @@ func (r *PaymentResource) Post(context smolder.APIContext, data interface{}, req
fmt.Println(string(body))
if resp.StatusCode != http.StatusOK {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, nil, smolder.NewErrorResponse(
http.StatusBadRequest,
false,
"Unknown payment ID",
"PaymentResource POST"))
return
......@@ -126,9 +123,8 @@ func (r *PaymentResource) Post(context smolder.APIContext, data interface{}, req
err = json.Unmarshal(body, &payments)
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Error decoding payment response",
"PaymentResource POST"))
return
......@@ -146,9 +142,8 @@ func (r *PaymentResource) Post(context smolder.APIContext, data interface{}, req
payment.CreatedAt = payments.Payments[0].CreatedAt
default:
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, nil, smolder.NewErrorResponse(
http.StatusBadRequest,
false,
"Unknown payment source",
"PaymentResource POST"))
return
......
......@@ -58,9 +58,8 @@ func (r *ProjectResource) Post(context smolder.APIContext, data interface{}, req
ups := data.(*ProjectPostStruct)
_, err := ctx.LoadProjectBySlug(ups.Project.Slug)
if err == nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, nil, smolder.NewErrorResponse(
http.StatusBadRequest,
false,
"A project with this slug address already exists",
"ProjectResource POST"))
return
......@@ -92,10 +91,8 @@ func (r *ProjectResource) Post(context smolder.APIContext, data interface{}, req
err = project.Save(ctx)
if err != nil {
panic(err)
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't create project",
"ProjectResource POST"))
return
......@@ -110,9 +107,8 @@ func (r *ProjectResource) Post(context smolder.APIContext, data interface{}, req
}
err = budget.Save(ctx)
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't create budget for new project",
"ProjectResource POST"))
return
......
......@@ -60,9 +60,8 @@ func (r *ProjectResource) Put(context smolder.APIContext, data interface{}, requ
err = project.Update(context.(*db.APIContext))
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't update project",
"ProjectResource PUT"))
return
......
......@@ -105,9 +105,8 @@ func (r *SessionResource) Post(context smolder.APIContext, data interface{}, req
var err error
user, err = context.(*db.APIContext).GetUserByNameAndPassword(sps.Username, sps.Password)
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusUnauthorized,
false,
err,
"SessionResource PUT"))
return
......@@ -116,9 +115,8 @@ func (r *SessionResource) Post(context smolder.APIContext, data interface{}, req
uuid, err := db.UUID()
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
http.StatusBadRequest,
false,
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
"Can't create authtoken",
"SessionResource PUT"))
return
......@@ -127,9 +125,8 @@ func (r *SessionResource) Post(context smolder.APIContext, data interface{}, req
user.AuthToken = append(user.AuthToken, uuid)
err = user.Update(context.(*db.APIContext))
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't update user session",
"SessionResource POST"))
return
......
......@@ -68,9 +68,8 @@ func (r *UserResource) Get(context smolder.APIContext, request *restful.Request,
} else {
auth, err := context.Authentication(request)
if err != nil || auth == nil || auth.(db.User).ID != 1 {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusUnauthorized,
false,
"Admin permission required for this operation",
"UserResource GET"))
return
......
......@@ -87,9 +87,8 @@ func (r *UserResource) Post(context smolder.APIContext, data interface{}, reques
}
if err != nil {
smolder.ErrorResponseHandler(request, response, smolder.NewErrorResponse(
smolder.ErrorResponseHandler(request, response, err, smolder.NewErrorResponse(
http.StatusInternalServerError,
true,
"Can't create user",
"UserResource POST"))
return
......