development and linting

pull/3/head
Dan Gowans 2025-03-28 12:36:56 -04:00
parent 66a697e097
commit 922603a1f2
15 changed files with 87 additions and 70 deletions

View File

@ -1,4 +1,4 @@
# Thank you for your interest in making the Lot Occupancy System better
# Thank you for your interest in making Sunrise CMS better
Together, we can build high quality software that meets the needs of municipalities,
while remaining open and budget conscious.

View File

@ -15,15 +15,32 @@
This is a major refactoring of the now archived
[Lot Occupancy System](https://github.com/cityssm/lot-occupancy-system),
originally built with multiple focuses. This fork completely reworks the project
originally built with multiple focuses in mind. This fork completely reworks the project
to focus exculsively on cemetery management.
## Why Sunrise CMS?
### ✔️ Cemetery Maps are NOT Required
Many cemetery applications rely on maps to drill down into burial sites.
In Sunrise CMS, maps are completely optional, greatly reducing the effort needed to get started.
### ✔️ The System Requirements are Very Low
Sunrise CMS does not need an expensive server to run.
No separate database server is required either.
The whole application could run on a current, modest workstation.
### ✔️ Sunrise CMS is Free and Open Source
There's no cost to use it.
## About this Project
- [Code of Conduct](CODE_OF_CONDUCT.md)
- [Contributing Guidelines](CONTRIBUTING.md)
- [Security Policy](SECURITY.md)
- [MIT Licence](LICENSE.md)
- 🤗 [Code of Conduct](CODE_OF_CONDUCT.md)
- 🥰 [Contributing Guidelines](CONTRIBUTING.md)
- 🛡️ [Security Policy](SECURITY.md)
- 📃 [MIT Licence](LICENSE.md)
Although the system is quite niche, it's being released in an open source environment in hopes to pool developer resources from other municipalities looking to move away from older, legacy systems.

View File

@ -2,36 +2,36 @@ import { config as cemeteryConfig } from './config.baseOntario.js';
export const config = { ...cemeteryConfig };
config.aliases.externalReceiptNumber = 'GP Receipt Number';
config.settings.burialSites.burialSiteNameSegments = {
separator: '-',
includeCemeteryKey: true,
separator: '-',
segments: {
1: {
isRequired: false,
isAvailable: true,
isRequired: false,
label: 'Block',
minLength: 1,
maxLength: 1
maxLength: 1,
minLength: 1
},
2: {
isRequired: false,
isAvailable: true,
isRequired: false,
label: 'Range',
minLength: 1,
maxLength: 3
maxLength: 3,
minLength: 1
},
3: {
isRequired: true,
isAvailable: true,
isRequired: true,
label: 'Lot',
minLength: 1,
maxLength: 4
maxLength: 4,
minLength: 1
},
4: {
isRequired: true,
isAvailable: true,
isRequired: true,
label: 'Grave',
minLength: 1,
maxLength: 2
maxLength: 2,
minLength: 1
}
}
};

View File

@ -7,36 +7,37 @@ export const config: Config = { ...cemeteryConfig }
config.aliases.externalReceiptNumber = 'GP Receipt Number'
config.settings.burialSites.burialSiteNameSegments = {
separator: '-',
includeCemeteryKey: true,
separator: '-',
segments: {
1: {
isRequired: false,
isAvailable: true,
isRequired: false,
label: 'Block',
minLength: 1,
maxLength: 1
maxLength: 1,
minLength: 1
},
2: {
isRequired: false,
isAvailable: true,
isRequired: false,
label: 'Range',
minLength: 1,
maxLength: 3
maxLength: 3,
minLength: 1
},
3: {
isRequired: true,
isAvailable: true,
isRequired: true,
label: 'Lot',
minLength: 1,
maxLength: 4
maxLength: 4,
minLength: 1
},
4: {
isRequired: true,
isAvailable: true,
isRequired: true,
label: 'Grave',
minLength: 1,
maxLength: 2
maxLength: 2,
minLength: 1
}
}
}

View File

@ -7,9 +7,9 @@ export declare const configDefaultValues: {
'application.httpPort': number;
'application.logoURL': string;
'application.maximumProcesses': number;
'application.ntfyStartup': ConfigNtfyStartup | undefined;
'application.userDomain': string;
'application.useTestDatabases': boolean;
'application.ntfyStartup': ConfigNtfyStartup | undefined;
'reverseProxy.disableCompression': boolean;
'reverseProxy.disableEtag': boolean;
'reverseProxy.urlPrefix': string;

View File

@ -6,9 +6,9 @@ export const configDefaultValues = {
'application.httpPort': 9000,
'application.logoURL': '/images/sunrise-cms.svg',
'application.maximumProcesses': 4,
'application.ntfyStartup': undefined,
'application.userDomain': '',
'application.useTestDatabases': false,
'application.ntfyStartup': undefined,
'reverseProxy.disableCompression': false,
'reverseProxy.disableEtag': false,
'reverseProxy.urlPrefix': '',
@ -30,15 +30,15 @@ export const configDefaultValues = {
'settings.longitudeMax': 180,
'settings.longitudeMin': -180,
'settings.burialSites.burialSiteNameSegments': {
separator: '-',
includeCemeteryKey: false,
separator: '-',
segments: {
1: {
isRequired: true,
isAvailable: true,
isRequired: true,
label: 'Plot Number',
minLength: 1,
maxLength: 20
maxLength: 20,
minLength: 1
}
}
},

View File

@ -17,11 +17,10 @@ export const configDefaultValues = {
'application.httpPort': 9000,
'application.logoURL': '/images/sunrise-cms.svg',
'application.maximumProcesses': 4,
'application.ntfyStartup': undefined as ConfigNtfyStartup | undefined,
'application.userDomain': '',
'application.useTestDatabases': false,
'application.ntfyStartup': undefined as ConfigNtfyStartup | undefined,
'reverseProxy.disableCompression': false,
'reverseProxy.disableEtag': false,
'reverseProxy.urlPrefix': '',
@ -49,15 +48,16 @@ export const configDefaultValues = {
'settings.longitudeMin': -180,
'settings.burialSites.burialSiteNameSegments': {
separator: '-',
includeCemeteryKey: false,
separator: '-',
segments: {
1: {
isRequired: true,
isAvailable: true,
isRequired: true,
label: 'Plot Number',
minLength: 1,
maxLength: 20
maxLength: 20,
minLength: 1
}
}
} as unknown as ConfigBurialSiteNameSegments,

View File

@ -1,9 +1,10 @@
import type { PoolConnection } from 'better-sqlite-pool'
import {
dateIntegerToString,
timeIntegerToPeriodString,
timeIntegerToString
} from '@cityssm/utils-datetime'
import type { PoolConnection } from 'better-sqlite-pool'
import type { BurialSiteComment } from '../types/recordTypes.js'

View File

@ -1,9 +1,9 @@
import type { BurialSiteType } from '../types/recordTypes.js';
interface GetFilters {
cemeteryId?: number | string;
}
interface BurialSiteTypeSummary extends BurialSiteType {
lotCount: number;
}
interface GetFilters {
cemeteryId?: number | string;
}
export default function getBurialSiteTypeSummary(filters: GetFilters): Promise<BurialSiteTypeSummary[]>;
export {};

View File

@ -2,14 +2,14 @@ import type { BurialSiteType } from '../types/recordTypes.js'
import { acquireConnection } from './pool.js'
interface GetFilters {
cemeteryId?: number | string
}
interface BurialSiteTypeSummary extends BurialSiteType {
lotCount: number
}
interface GetFilters {
cemeteryId?: number | string
}
export default async function getBurialSiteTypeSummary(
filters: GetFilters
): Promise<BurialSiteTypeSummary[]> {

View File

@ -6,15 +6,14 @@ export default async function getCemeteries() {
m.cemeteryLatitude, m.cemeteryLongitude, m.cemeterySvg,
m.cemeteryAddress1, m.cemeteryAddress2, m.cemeteryCity, m.cemeteryProvince, m.cemeteryPostalCode,
m.cemeteryPhoneNumber,
ifnull(l.burialSiteCount, 0) as burialSiteCount
count(b.burialSiteId) as burialSiteCount
from Cemeteries m
left join (
select cemeteryId, count(burialSiteId) as burialSiteCount
from BurialSites
where recordDelete_timeMillis is null
group by cemeteryId
) l on m.cemeteryId = l.cemeteryId
left join BurialSites b on m.cemeteryId = b.cemeteryId and b.recordDelete_timeMillis is null
where m.recordDelete_timeMillis is null
group by m.cemeteryId, m.cemeteryName, m.cemeteryDescription,
m.cemeteryLatitude, m.cemeteryLongitude, m.cemeterySvg,
m.cemeteryAddress1, m.cemeteryAddress2, m.cemeteryCity, m.cemeteryProvince, m.cemeteryPostalCode,
m.cemeteryPhoneNumber
order by m.cemeteryName, m.cemeteryId`)
.all();
database.release();

View File

@ -11,15 +11,14 @@ export default async function getCemeteries(): Promise<Cemetery[]> {
m.cemeteryLatitude, m.cemeteryLongitude, m.cemeterySvg,
m.cemeteryAddress1, m.cemeteryAddress2, m.cemeteryCity, m.cemeteryProvince, m.cemeteryPostalCode,
m.cemeteryPhoneNumber,
ifnull(l.burialSiteCount, 0) as burialSiteCount
count(b.burialSiteId) as burialSiteCount
from Cemeteries m
left join (
select cemeteryId, count(burialSiteId) as burialSiteCount
from BurialSites
where recordDelete_timeMillis is null
group by cemeteryId
) l on m.cemeteryId = l.cemeteryId
left join BurialSites b on m.cemeteryId = b.cemeteryId and b.recordDelete_timeMillis is null
where m.recordDelete_timeMillis is null
group by m.cemeteryId, m.cemeteryName, m.cemeteryDescription,
m.cemeteryLatitude, m.cemeteryLongitude, m.cemeterySvg,
m.cemeteryAddress1, m.cemeteryAddress2, m.cemeteryCity, m.cemeteryProvince, m.cemeteryPostalCode,
m.cemeteryPhoneNumber
order by m.cemeteryName, m.cemeteryId`
)
.all() as Cemetery[]

View File

@ -40,7 +40,7 @@ async function postHandler(request, response) {
else if (userName !== '' && passwordPlain !== '') {
isAuthenticated = await authenticate(userName, passwordPlain);
}
let userObject = undefined;
let userObject;
if (isAuthenticated) {
const userNameLowerCase = userName.toLowerCase();
const canLogin = getConfigProperty('users.canLogin').some((currentUserName) => userNameLowerCase === currentUserName.toLowerCase());

View File

@ -72,7 +72,7 @@ async function postHandler(
isAuthenticated = await authenticate(userName, passwordPlain)
}
let userObject: User | undefined = undefined
let userObject: User | undefined
if (isAuthenticated) {
const userNameLowerCase = userName.toLowerCase()

View File

@ -127,8 +127,8 @@
name="burialSiteNameSegment<%= segmentIndexString %>"
value="<%= burialSite[`burialSiteNameSegment${segmentIndex}`] %>"
type="text"
minlength="<%= Math.min(segment.minLength ?? 1, 20) %>"
maxlength="<%= Math.min(segment.maxLength ?? 20, 20) %>"
minlength="<%= Math.max(Math.min(segment.minLength ?? 1, 20), 1) %>"
maxlength="<%= Math.max(Math.min(segment.maxLength ?? 20, 20), 1) %>"
placeholder="<%= segment.label ?? '' %>"
aria-label="<%= segment.label ?? '' %>"
<%= (segment.isRequired ?? false) ? ' required' : '' %>