habits-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev bc888334fc51
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone e8a688fd21351d04b05468df
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
policy.json
{
  "auth": {
    "anonymous": true
  },
  "people/$userId": {
    "description": "A person's Habits settings (the name friends see on shared habits, the first day of the week) as a JSON string. Only that person can read or write it.",
    "fields": {
      "data": "String"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $userId",
      "create": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.data) <= 2000",
      "update": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.data) <= 2000",
      "delete": "false"
    }
  },
  "people/$userId/habits/$habitId": {
    "description": "One habit with its schedule and every check-in and note, as a JSON string. Only that person can read or write it. The device keeps the same data, so Habits works offline. A deleted habit stays as a small marker so another device does not bring it back.",
    "fields": {
      "data": "String"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $userId",
      "create": "@user.id != null && @user.id == $userId && @StringUtils.length($habitId) >= 6 && @StringUtils.length($habitId) <= 32 && @StringUtils.length(@newData.data) <= 300000",
      "update": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.data) <= 300000",
      "delete": "false"
    }
  },
  "teams/$teamId": {
    "description": "A habit shared by link between friends: its name and schedule. Readable by anyone who has opened the link (a join below); the random 20-character id is the link's secret, so nobody can list shared habits.",
    "fields": {
      "name": "String!",
      "kind": "String!",
      "target": "UInt!",
      "unit": "String!",
      "schedule": "String!",
      "createdBy": "String!",
      "createdAt": "UInt!"
    },
    "rules": {
      "read": "@user.id != null && get(/teams/$teamId/joins/@user.id) != null",
      "create": "@user.id != null && @newData.createdBy == @user.id && @newData.createdAt <= @time.now && @StringUtils.length($teamId) == 20 && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 60 && (@newData.kind == 'check' || @newData.kind == 'count' || @newData.kind == 'avoid') && @newData.target >= 1 && @newData.target <= 10000 && @StringUtils.length(@newData.unit) <= 20 && @StringUtils.length(@newData.schedule) <= 200 && getAfter(/teams/$teamId/joins/@user.id) != null",
      "update": "false",
      "delete": "false"
    }
  },
  "teams/$teamId/joins/$userId": {
    "description": "Someone who opened a shared habit's link. Knowing the id is what lets you join, and joining only lets you see the streaks on that one habit.",
    "fields": {
      "at": "UInt!"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $userId",
      "create": "@user.id != null && @user.id == $userId && @newData.at <= @time.now && getAfter(/teams/$teamId) != null",
      "update": "false",
      "delete": "@user.id != null && @user.id == $userId"
    }
  },
  "teams/$teamId/members/$userId": {
    "description": "One person's streak on a shared habit: the name they chose, current and best streak, its unit (day, week, time, or without for an avoid habit) and the last day they did it (YYYY-MM-DD). Only that person writes it; everyone who opened the link reads it. Nothing else about anyone's habits is shared.",
    "fields": {
      "name": "String",
      "streak": "UInt",
      "best": "UInt",
      "unit": "String",
      "last": "String"
    },
    "rules": {
      "read": "@user.id != null && get(/teams/$teamId/joins/@user.id) != null",
      "create": "@user.id != null && @user.id == $userId && getAfter(/teams/$teamId/joins/@user.id) != null && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 40 && @newData.best >= @newData.streak && @newData.best <= 100000 && (@newData.unit == 'day' || @newData.unit == 'week' || @newData.unit == 'time' || @newData.unit == 'without') && (@StringUtils.length(@newData.last) == 0 || @StringUtils.length(@newData.last) == 10)",
      "update": "@user.id != null && @user.id == $userId && get(/teams/$teamId/joins/@user.id) != null && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 40 && @newData.best >= @newData.streak && @newData.best <= 100000 && (@newData.unit == 'day' || @newData.unit == 'week' || @newData.unit == 'time' || @newData.unit == 'without') && (@StringUtils.length(@newData.last) == 0 || @StringUtils.length(@newData.last) == 10)",
      "delete": "@user.id != null && @user.id == $userId"
    }
  },
  "constants": {
    "AGENT_ADDRESS": "Hw4Dtxudp6vRBEvCyTaHw8Qpv7QfWSHafDNWef5zt24A",
    "AGENT_ADDRESS_BOUND": true
  }
}

Change history

Point OpenApps links to openapps.free
bc888334fc · 18 hours ago
Bind agent address in policy
1f744a2b83 · 19 hours ago
Use OpenApp service URLs and add contribution strip
a9bb601ee0 · 19 hours ago
Bounded source sync
5646afc3a0 · 1 day ago
Bounded source sync
f1b1bac8d6 · 1 day ago
Bounded source sync
b1e14b41b0 · 1 day ago