or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

admin-integration.mdapi-endpoints.mdconfiguration.mdcore-system.mdindex.mdsignals.mdtemplate-integration.mdutilities.mdweb-interface.md

api-endpoints.mddocs/

0

# API Endpoints

1

2

JSON API endpoints for real-time notification features including unread counts, notification lists, and AJAX-powered live updates for single-page applications and mobile apps.

3

4

## Capabilities

5

6

### Notification Count Endpoints

7

8

JSON endpoints that return notification counts for authenticated users with caching and performance optimization.

9

10

```python { .api }

11

@never_cache

12

def live_unread_notification_count(request):

13

"""

14

Return JSON with unread notification count for authenticated user.

15

16

Args:

17

request: Django HTTP request object

18

19

Returns:

20

JsonResponse: {"unread_count": <number>}

21

22

Response for unauthenticated users:

23

{"unread_count": 0}

24

25

Example response:

26

{"unread_count": 5}

27

"""

28

29

@never_cache

30

def live_all_notification_count(request):

31

"""

32

Return JSON with total notification count for authenticated user.

33

34

Args:

35

request: Django HTTP request object

36

37

Returns:

38

JsonResponse: {"all_count": <number>}

39

40

Response for unauthenticated users:

41

{"all_count": 0}

42

43

Example response:

44

{"all_count": 25}

45

"""

46

```

47

48

### Notification List Endpoints

49

50

JSON endpoints that return formatted notification lists with configurable limits and optional mark-as-read functionality.

51

52

```python { .api }

53

@never_cache

54

def live_unread_notification_list(request):

55

"""

56

Return JSON with unread notification list and count.

57

58

Args:

59

request: Django HTTP request object

60

61

Query Parameters:

62

max (int, optional): Maximum notifications to return (1-100, default from settings)

63

mark_as_read (str, optional): Mark returned notifications as read if present

64

65

Returns:

66

JsonResponse: {

67

"unread_count": <number>,

68

"unread_list": [<notification_objects>]

69

}

70

71

Response for unauthenticated users:

72

{"unread_count": 0, "unread_list": []}

73

74

Example response:

75

{

76

"unread_count": 3,

77

"unread_list": [

78

{

79

"id": 123,

80

"slug": 110909,

81

"actor": "John Doe",

82

"verb": "liked",

83

"target": "My Blog Post",

84

"action_object": null,

85

"description": "John liked your post",

86

"timestamp": "2023-01-15T10:30:00Z",

87

"unread": true,

88

"level": "info",

89

"data": {}

90

},

91

// ... more notifications

92

]

93

}

94

"""

95

96

@never_cache

97

def live_all_notification_list(request):

98

"""

99

Return JSON with all notification list and count.

100

101

Args:

102

request: Django HTTP request object

103

104

Query Parameters:

105

max (int, optional): Maximum notifications to return (1-100, default from settings)

106

mark_as_read (str, optional): Mark returned notifications as read if present

107

108

Returns:

109

JsonResponse: {

110

"all_count": <number>,

111

"all_list": [<notification_objects>]

112

}

113

114

Response for unauthenticated users:

115

{"all_count": 0, "all_list": []}

116

117

Example response:

118

{

119

"all_count": 25,

120

"all_list": [

121

{

122

"id": 123,

123

"slug": 110909,

124

"actor": "John Doe",

125

"verb": "liked",

126

"target": "My Blog Post",

127

"action_object": null,

128

"description": "John liked your post",

129

"timestamp": "2023-01-15T10:30:00Z",

130

"unread": false,

131

"level": "info",

132

"data": {}

133

},

134

// ... more notifications

135

]

136

}

137

"""

138

```

139

140

### Usage Examples

141

142

#### Basic AJAX Requests

143

144

```javascript

145

// Get unread notification count

146

async function getUnreadCount() {

147

try {

148

const response = await fetch('/inbox/notifications/api/unread_count/');

149

const data = await response.json();

150

return data.unread_count;

151

} catch (error) {

152

console.error('Error fetching unread count:', error);

153

return 0;

154

}

155

}

156

157

// Get unread notification list

158

async function getUnreadNotifications(maxCount = 10) {

159

try {

160

const response = await fetch(`/inbox/notifications/api/unread_list/?max=${maxCount}`);

161

const data = await response.json();

162

return data;

163

} catch (error) {

164

console.error('Error fetching notifications:', error);

165

return { unread_count: 0, unread_list: [] };

166

}

167

}

168

169

// Get all notifications

170

async function getAllNotifications(maxCount = 20) {

171

try {

172

const response = await fetch(`/inbox/notifications/api/all_list/?max=${maxCount}`);

173

const data = await response.json();

174

return data;

175

} catch (error) {

176

console.error('Error fetching all notifications:', error);

177

return { all_count: 0, all_list: [] };

178

}

179

}

180

```

181

182

#### Live Notification Badge

183

184

```javascript

185

class NotificationBadge {

186

constructor(badgeElement, apiUrl = '/inbox/notifications/api/unread_count/') {

187

this.badge = badgeElement;

188

this.apiUrl = apiUrl;

189

this.updateInterval = 30000; // 30 seconds

190

this.startPolling();

191

}

192

193

async updateBadge() {

194

try {

195

const response = await fetch(this.apiUrl);

196

const data = await response.json();

197

const count = data.unread_count;

198

199

this.badge.textContent = count;

200

this.badge.style.display = count > 0 ? 'inline' : 'none';

201

202

// Add visual indication for new notifications

203

if (count > this.lastCount) {

204

this.badge.classList.add('pulse');

205

setTimeout(() => this.badge.classList.remove('pulse'), 1000);

206

}

207

208

this.lastCount = count;

209

} catch (error) {

210

console.error('Failed to update notification badge:', error);

211

}

212

}

213

214

startPolling() {

215

this.updateBadge(); // Initial update

216

this.intervalId = setInterval(() => this.updateBadge(), this.updateInterval);

217

}

218

219

stopPolling() {

220

if (this.intervalId) {

221

clearInterval(this.intervalId);

222

}

223

}

224

}

225

226

// Usage

227

const badge = document.querySelector('.notification-badge');

228

const notificationBadge = new NotificationBadge(badge);

229

```

230

231

#### Live Notification List

232

233

```javascript

234

class NotificationList {

235

constructor(containerElement, apiUrl = '/inbox/notifications/api/unread_list/') {

236

this.container = containerElement;

237

this.apiUrl = apiUrl;

238

this.updateInterval = 15000; // 15 seconds

239

this.startPolling();

240

}

241

242

async updateList() {

243

try {

244

const response = await fetch(`${this.apiUrl}?max=10`);

245

const data = await response.json();

246

247

this.renderNotifications(data.unread_list);

248

this.updateCount(data.unread_count);

249

} catch (error) {

250

console.error('Failed to update notification list:', error);

251

}

252

}

253

254

renderNotifications(notifications) {

255

this.container.innerHTML = '';

256

257

if (notifications.length === 0) {

258

this.container.innerHTML = '<li class="no-notifications">No new notifications</li>';

259

return;

260

}

261

262

notifications.forEach(notification => {

263

const li = document.createElement('li');

264

li.className = 'notification-item';

265

li.innerHTML = `

266

<div class="notification-content">

267

<strong>${notification.actor}</strong> ${notification.verb}

268

${notification.target ? ` ${notification.target}` : ''}

269

<small>${this.formatTime(notification.timestamp)}</small>

270

</div>

271

<div class="notification-actions">

272

<button onclick="markAsRead(${notification.slug})">Mark as Read</button>

273

</div>

274

`;

275

this.container.appendChild(li);

276

});

277

}

278

279

formatTime(timestamp) {

280

const date = new Date(timestamp);

281

const now = new Date();

282

const diffMs = now - date;

283

const diffMins = Math.floor(diffMs / 60000);

284

285

if (diffMins < 1) return 'just now';

286

if (diffMins < 60) return `${diffMins}m ago`;

287

if (diffMins < 1440) return `${Math.floor(diffMins / 60)}h ago`;

288

return `${Math.floor(diffMins / 1440)}d ago`;

289

}

290

291

updateCount(count) {

292

const countElement = document.querySelector('.notification-count');

293

if (countElement) {

294

countElement.textContent = count;

295

}

296

}

297

298

startPolling() {

299

this.updateList(); // Initial load

300

this.intervalId = setInterval(() => this.updateList(), this.updateInterval);

301

}

302

303

stopPolling() {

304

if (this.intervalId) {

305

clearInterval(this.intervalId);

306

}

307

}

308

}

309

310

// Usage

311

const listContainer = document.querySelector('.notification-list');

312

const notificationList = new NotificationList(listContainer);

313

```

314

315

#### Mark as Read with API

316

317

```javascript

318

// Get notifications and mark them as read

319

async function getAndMarkNotifications() {

320

try {

321

const response = await fetch('/inbox/notifications/api/unread_list/?mark_as_read=true');

322

const data = await response.json();

323

324

// Process notifications that were automatically marked as read

325

console.log(`Retrieved and marked ${data.unread_list.length} notifications as read`);

326

return data;

327

} catch (error) {

328

console.error('Error:', error);

329

}

330

}

331

```

332

333

#### Real-time Dashboard Widget

334

335

```javascript

336

class NotificationWidget {

337

constructor(options = {}) {

338

this.options = {

339

updateInterval: 30000,

340

maxNotifications: 5,

341

showBadge: true,

342

showList: true,

343

...options

344

};

345

346

this.init();

347

}

348

349

init() {

350

this.createWidget();

351

this.startUpdates();

352

}

353

354

createWidget() {

355

this.widget = document.createElement('div');

356

this.widget.className = 'notification-widget';

357

this.widget.innerHTML = `

358

<div class="widget-header">

359

<span>Notifications</span>

360

${this.options.showBadge ? '<span class="badge">0</span>' : ''}

361

</div>

362

${this.options.showList ? '<ul class="notification-list"></ul>' : ''}

363

`;

364

document.body.appendChild(this.widget);

365

}

366

367

async update() {

368

const [countData, listData] = await Promise.all([

369

fetch('/inbox/notifications/api/unread_count/').then(r => r.json()),

370

this.options.showList ?

371

fetch(`/inbox/notifications/api/unread_list/?max=${this.options.maxNotifications}`)

372

.then(r => r.json()) :

373

Promise.resolve(null)

374

]);

375

376

// Update badge

377

if (this.options.showBadge) {

378

const badge = this.widget.querySelector('.badge');

379

badge.textContent = countData.unread_count;

380

badge.style.display = countData.unread_count > 0 ? 'inline' : 'none';

381

}

382

383

// Update list

384

if (this.options.showList && listData) {

385

this.renderList(listData.unread_list);

386

}

387

}

388

389

renderList(notifications) {

390

const list = this.widget.querySelector('.notification-list');

391

list.innerHTML = notifications.map(n => `

392

<li class="notification-item" data-id="${n.id}">

393

<div class="notification-text">

394

${n.description || `${n.actor} ${n.verb}`}

395

</div>

396

<div class="notification-time">

397

${this.formatTime(n.timestamp)}

398

</div>

399

</li>

400

`).join('');

401

}

402

403

formatTime(timestamp) {

404

return new Date(timestamp).toLocaleDateString();

405

}

406

407

startUpdates() {

408

this.update(); // Initial update

409

this.intervalId = setInterval(() => this.update(), this.options.updateInterval);

410

}

411

412

destroy() {

413

if (this.intervalId) {

414

clearInterval(this.intervalId);

415

}

416

if (this.widget) {

417

this.widget.remove();

418

}

419

}

420

}

421

422

// Usage

423

const widget = new NotificationWidget({

424

updateInterval: 20000, // Update every 20 seconds

425

maxNotifications: 8,

426

showBadge: true,

427

showList: true

428

});

429

```

430

431

#### Error Handling and Fallbacks

432

433

```javascript

434

class RobustNotificationClient {

435

constructor() {

436

this.retryCount = 0;

437

this.maxRetries = 3;

438

this.retryDelay = 5000;

439

}

440

441

async fetchWithRetry(url, options = {}) {

442

try {

443

const response = await fetch(url, options);

444

445

if (!response.ok) {

446

throw new Error(`HTTP ${response.status}: ${response.statusText}`);

447

}

448

449

this.retryCount = 0; // Reset on success

450

return await response.json();

451

} catch (error) {

452

if (this.retryCount < this.maxRetries) {

453

this.retryCount++;

454

console.warn(`Fetch failed, retrying in ${this.retryDelay}ms (attempt ${this.retryCount}/${this.maxRetries})`);

455

456

await new Promise(resolve => setTimeout(resolve, this.retryDelay));

457

return this.fetchWithRetry(url, options);

458

} else {

459

console.error('Max retries exceeded:', error);

460

throw error;

461

}

462

}

463

}

464

465

async getNotifications() {

466

try {

467

return await this.fetchWithRetry('/inbox/notifications/api/unread_list/');

468

} catch (error) {

469

// Return fallback data

470

return { unread_count: 0, unread_list: [] };

471

}

472

}

473

}

474

```